swoole协程+zephir纯php开发大型RPG微信小游戏(已开源)

   日期:2020-04-29     浏览:100    评论:0    
核心提示:概述我在一年前,帮助朋友构建游戏RPG后台的时候,思考如何兼顾开发效率和性能,最终想到了 php +php

概述

我在一年前,帮助朋友构建游戏RPG后台的时候,思考如何兼顾开发效率和性能,最终想到了 php + swoole协程 + swoole_orm + zephir ,微信小游戏搜索:“剑的传说”

swoole协程有着极高的IO并发能力

swoole_orm 是我开发的php 扩展,有着非常高的性能、sql安全性和开发效率,开源地址为: https://github.com/swoole/ext-orm

游戏的战斗部分完全用 zephir 来实现,宣称像写php一样写php扩展,能做到同时兼顾性能和开发效率,(zephir 代码有机会我再开源出来,目前时机不成熟,游戏还比较火热)

后台框架开源地址:https://github.com/caohao-php/ycsocket

仅展示部分zephir代码:

代码结构

———————————————— 
|--- server.php               //启动入口 
|--- system                   //框架系统代码
|--- application              //业务代码 
         |----- config        //配置目录
         |----- controller    //控制器目录
                |------ Game.php    //Game控制器
         |----- dao           //数据层
         |----- library       //公用类库
         |----- service       //业务层

请求路由

webSocket.send('{"c":"game","m":"ver", "userid":123593}');

输入参数为json, 根据 c 和 m 参数,路由到 controller/Game.php 下 verAction 函数。路由逻辑在 Application->run() 方法中,
路由之前,首先会调用 Filter::auth($params) 对参数验签,我们可以在该函数中加入自己的签名验证逻辑。

//system/Application.php
class Application
{
    public function run(& $params, $clientInfo)
    {
        $ret = Filter::auth($params);
        if ($ret != 0) {
            return $ret;
        }

        foreach ($params as $k => $v) {
            $params[$k] = trim($v);
        }

        $controller = ucfirst($params['c']);
        $action = $params['m'] . "Action";
        $class_name = $controller . "Controller";

        try {
            $obj = new $class_name($params, $clientInfo);

            if (!method_exists($obj, $action)) {
                unset($obj);
                show_404("$controller/$action");
                return $this->response_error(3, "route error");
            }

            $ret = $obj->$action();
            unset($obj);
            return $ret;
        } catch (Exception $e) {
            unset($obj);

            if ($e instanceof LogicException) { //业务异常
                $errorcode = $e->getCode() == 0 ? 8 : $e->getCode();
                return $this->response_error($errorcode, $e->getMessage());
            } else if ($e->getMessage() != 'swoole exit.') {
                Logger::error("Catch An Exception File=[" . $e->getFile() . "|" . $e->getLine() . "] Code=[" . $e->getCode() . "], Message=[" . $e->getMessage() . "]", "exception_log");

                echo "Catch An Exception \n";
                echo "File:" . $e->getFile() . "\n";
                echo "Line:" . $e->getLine() . "\n";
                echo "Code:" . $e->getCode() . "\n";
                echo "Message:" . $e->getMessage() . "\n";
                return $this->response_error(99, "system exception");
            } else {
                echo "swoole exit.\n";
                return $this->response_error(99, "application exit");
            }
        }
    }

       ...
}

控制器Controller

所有控制器位于:application/controllers 目录下,继承自SuperController,父类SuperController 的构造函数中会调用$this->init()函数,所以你的控制器如果有初始化任务,请写在 init 函数里。

提供4个返回函数:

response_error 返回报错信息给自己

response_success_to_all 返回数据给当前所有玩家,例如世界聊天

response_success_to_me 返回数据给自己

response_success_to_uids 返回数据给指定uid,在 server.php ,数据接入的时候,我们会将 uid 绑定到 socket fd 上面去

//server.php
$uid = intval($input['userid']);
if($uid > 0) {
    Connector::set_fd($uid, $ws);
}
class GameController extends SuperController
{
    var $game_service;
    var $userinfo_service;

    public function init()
    {
        $this->userinfo_service = $this->loader->service('UserinfoService');
        $this->game_service = $this->loader->service('GameService');

        $this->util_log = $this->loader->logger('game_log');
    }

    //聊天接口
    public function chatAction()
    {
        $userId = $this->params['userid'];
        $type = intval($this->params['type']);  //0-世界 1-私聊
        $token = $this->params['token'];
        $nickname = $this->params['nickname'];
        $avatar_url = $this->params['avatar_url'];
        $content = $this->params['content'];
        $to_userid = intval($this->params['to_userid']);

        $this->userinfo_service->getZoneUserAndAuth($userId, $token);

        if (empty($content)) {
            return $this->response_error(13342339, '内容不能为空');
        }

        $result = array();
        $result['userid'] = $userId;
        $result['type'] = $type;
        $result['nickname'] = $nickname;
        $result['avatar_url'] = $avatar_url;
        $result['gender'] = $this->params['gender'];
        $result['vip_level'] = $this->params['vip_level'];
        $result['lv'] = $this->params['lv'];
        $result['content'] = $content;

        if ($type == 0) {
            return $this->response_success_to_all($result);
        } else if ($type == 1) {
            return $this->response_success_to_uids([$userId, $to_userid], $result);
        }
    }
}

过滤验签

application/Filter.php , 在 auth 中写入验签方法,所有接口都会在这里校验, 所有GET、POST等参数放在 $params 里。

class Filter
{
    //验签过程
    public static function auth(& $params)
    {
        

        //验签成功
        return 0;
    }

    public static function response_error($code, $message)
    {
        $data = array("code" => $code, "msg" => $message);
        $result['send_user'] = "me";
        $result['msg'] = json_encode($data);
        return $result;
    }
}

加载器

通过 Loader 加载器可以加载业务层,dao层,公共库,日志、配置等对象, Logger 为日志类。

$this->game_service = $this->loader->service('GameService');
$this->game_dao = $this->loader->dao("GameDao");
$this->util_log = $this->loader->logger('game_log');
$this->util_lib = $this->loader->library('Utillib');
$this->conf = $this->loader->config('config');

业务层

通过 $this->game_service = $this->loader->service(‘GameService’); 去加载业务层。

Service 继承自 SuperService,在 init() 函数里面实现对象初始化内容。

class GameService extends SuperService
{
    public function init()
    {
        parent::init();
        $this->game_dao = $this->loader->dao("GameDao");
        $this->userinfo_service = $this->loader->dao("UserinfoService");
        $this->util_log = $this->loader->logger('game_log');
    }

    //用户充值
    public function get_user_vip_contents($userid)
    {
        $data = $this->game_dao->get_user_vip_contents($userid);

        if (empty($data['content'])) {
            $content = array();
            $content['leiji_xiaofei'] = 0;  //累计消费
            $content['leiji_chong'] = 0;  //累计充值
            $content['jijin']['status'] = 0;  //是否购买成长基金 0-未购买 1-已购买
            $this->game_dao->insert_user_vip_contents($userid, $content);
        } else {
            $content = json_decode($data['content'], true);
        }

        return $content;
    }

    //更新充值信息
    public function update_user_vip_contents($userid, $content)
    {
        return $this->game_dao->update_user_vip_contents($userid, $content);
    }
    
    ...
}

Dao层

所有与Redis、MySQL等等存储介质打交道的逻辑,最好都放在Dao层,

dao对象通过 $this->game_dao = $this->loader->dao(“GameDao”); 加载。

Dao层继承自 SuperDao,在 init() 函数里面实现对象初始化内容。
SuperDao 提供了许多快速操作数据库的方法,如果你需要用到 SuperDao 的快速操作数据库的函数,
你最好指定以下数据库、缓存配置,因为默认他们是 default, 这些配置位于application/config 目录下的 database.php 和 redis.php 中。

$this->redis_name = “default”;

$this->db_name = “default”;

class GameDao extends SuperDao
{
    public function init()
    {
        $this->db_name = "game";
        $this->util_log = $this->loader->logger('game_log');
    }
    
    //user_vip_contents 表
    public function get_user_vip_contents($userid)
    {
        $key = 'pre_vip_contents_' . $userid;
        $data = $this->get_one_table_data('user_vip_contents', ['user_id' => $userid], $key);
        return $data;
    }

    public function insert_user_vip_contents($userid, $content)
    {
        $key = 'pre_vip_contents_' . $userid;
        return $this->insert_table('user_vip_contents', ['user_id' => $userid, 'content' => json_encode($content)], $key);
    }
    
    ...
}
//数据库配置 database.php
$util_db_config['default']['host'] = '127.0.0.1';
$util_db_config['default']['username'] = 'test';
$util_db_config['default']['password'] = 'test';
$util_db_config['default']['dbname'] = 'user';
$util_db_config['default']['char_set'] = 'utf8';
$util_db_config['default']['dbcollat'] = 'utf8_general_ci';
$util_db_config['default']['pool_size'] = 10;

//redis配置 redis.php
$util_redis_conf['userinfo']['host'] = '127.0.0.1';
$util_redis_conf['userinfo']['port'] = 6381;
$util_redis_conf['userinfo']['auth'] = 'o01nc7vgd65xa';

//使用方法
MySQLPool::instance('default')->query($sql);
MySQLPool::instance('default')->get($table, $where, $column);
RedisPool::instance('userinfo')->set('test', 123);
RedisPool::instance('userinfo')->expire('test', 86400);

library库

第三方类库都存在于 application/library 目录下 ,通过$this->utillib = $this->loader->library(“Utillib”); 实例化。

日志

日志可以通过 loader 实例化,实例化的日志会打印有请求参数和客户端IP等信息,也可以用得静态函数,不过静态函数无法获取则请求参数或者客户端IP等信息。

日志路径在 server.php 中配置,记得把 /data/app/logs 的权限设置高些,define(‘LOG_PATH’, ‘/data/app/logs/super_server’); //日志目录

日志分如下5个级别:

const DEBUG = ‘DEBUG’;

const WARN = ‘WARN’;

class GameService extends SuperService
{
    public function init()
    {
        parent::init();
        $this->util_log = $this->loader->logger('game_log');
    }
    
    public funciton test() 
    {
        $this->util_log->LogInfo("info test");
    $this->util_log->LogNotice("notice test");
    $this->util_log->LogWarn("warning test");
    $this->util_log->LogError("error test");
    }
    
    public funciton static_test() 
    {
        Logger::info("static info test");
    Logger::notice("static notice test");
    Logger::warn("static warning test");
    Logger::error("static error test");
    
    }
}

附录 - CoreModel 中的辅助极速开发函数(不关心可以跳过)


public function hget_redis($redis_key, $field);

public function hset_redis($redis_key, $field, $data, $redis_expire = 600, $set_empty_flag = true);

public function get_redis($redis_key)

public function set_redis($redis_key, $data, $redis_expire = 600, $set_empty_flag = true);

public function clear_redis_cache($redis_key = "");

public function insert_table($table, $data, $redis_key = "");

public function update_table($table, $where, $data, $redis_key = "");

public function replace_table($table, $data, $redis_key = "");

public function delete_table($table, $where, $redis_key = "");

public function get_table_data($table, $where = array(), $redis_key = "", $redis_expire = 600, $column = "*", $set_empty_flag = true);

public function get_one_table_data($table, $where, $redis_key = "", $redis_expire = 600, $column = "*", $set_empty_flag = true);
 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服