Laravel 使用 Swoole 代码热更新方案

业务背景

做的是一款游戏匹配的App,PHP使用swoole创建websocket提供游戏的匹配服务

匹配流程如下

  • 对匹配者的鉴权(握手事件处理)
  • 匹配的业务逻辑(比如男只能匹配到女,这块也是需要热更新,open事件处理)
  • 匹配成功返回数据,关闭连接
  • 再往后就是nodejs去提供服务

想要达到的目的

在不重启服务的情况下,改变了匹配的业务逻辑代码的情况下自动热更新代码

关于热更新swoole官方文档

其实核心就是说你要热更新的代码必须在onWorkerStart事件中引入

安装swoole和inotify

自己绘制的"设计图"

如果你使用artisan启动swoole服务的话,可能会热更新失败,因为在onWorkerStart,之前已经载入太多类

index

设置常量同时实例化MatchServer来启动服务

require 'MatchServer.php';
if (php_sapi_name() != 'cli') die('请用cli模式启动');
define('ROOT_PATH',dirname(dirname(dirname(__DIR__))).'/');
define('PORT',20005);
$server = new MatchServer();

MatchServer

class MatchServer{
    private $server;
    protected $application;

    function __construct ()
    {
        // 创建swoole_table,用于进程间数据共享
        $table = new swoole_table(1024);
        $table->column('fd', swoole_table::TYPE_INT);
        $table->column('uid', swoole_table::TYPE_INT);
        $table->column('gameType', swoole_table::TYPE_STRING, 256);
        $table->column('data', swoole_table::TYPE_STRING, 256);
        $table->create();

        $this->server = new swoole_websocket_server("0.0.0.0", PORT);
        $this->server->table = $table;

        // 注册回调事件
        $this->server->on('handShake', array($this, 'onHandShake'));
        $this->server->on('workerStart', array($this, 'onWorkerStart'));
        $this->server->on('open', array($this, 'onOpen'));
        $this->server->on('message', array($this, 'onMessage'));
        $this->server->on('close', array($this, 'onClose'));

        $this->server->start();
    }

    /**
     * 处理握手
     * 
     * @param swoole_http_request  $request
     * @param swoole_http_response $response
     *
     * @return bool
     */
    public function onHandShake (\swoole_http_request $request, \swoole_http_response $response)
    {
        if(参数校验不通过)
        {
            $response->end();
            return false;
        }
        //swoole握手环节,因为我的匹配是在open事件处理,当自己处理握手之后,不会自动调用open事件,需自己调用

        // 握手环节代码..太多..考虑到篇幅问题,不贴了..大家可以去swoole手册搜索
        $this->onOpen($this->server, $request);

        return true;
    }

    /**
     * 载入框架入口文件,并设置inotify热更新目录
     *
     * @param $server
     * @param $worker_id
     */
    public function onWorkerStart ($server, $worker_id)
    {
        // 载入框架入口文件
        require ROOT_PATH.'public/index.php';
        // 实例化业务逻辑类
        $this->application = new MatchApplication();
        if ($worker_id == 0) {
                // 设置热更新目录
                $dir = app_path('Game/Match');
                $list[] = $dir;
                foreach (array_diff(scandir($dir), array('.', '..')) as $item) {
                        $list[] = $dir.'/'.$item;
                }

                $notify = inotify_init();
                foreach ($list as $item) {
                        inotify_add_watch($notify, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
                }
                swoole_event_add($notify, function () use ($notify,$server) {
                        $events = inotify_read($notify);
                        if (!empty($events)) {
                                // 执行swolle reload
                                $server->reload();
                        }
                });
        }
    }

    /**
     * 处理匹配
     * 
     * @param $server
     * @param $request
     */
    public function onOpen ($server, $request)
    {
        // 调用业务逻辑类的onOpen
        $this->application->onOpen($server,$request);
    }

    public function onMessage ($server, $frame){}

     /**
     * 关闭连接同时删除swoole_table数据
     * 
     * @param $server
     * @param $fd
     */
    public function onClose ($server, $fd)
    {
        // 由于我进程间的数据共享用的swoole_table,所以连接关闭,需要删除数据
        if ($server->table->exist($fd)) {
            $server->table->del($fd);
        }
    }
}

MatchApplication

/**
 * 处理匹配业务逻辑
 * 
 * @param $server
 * @param $request
 */
public function onOpen ($server, $request)
{
    $fd = $request->fd;
    // 处理业务逻辑......
    $server->push($fd,$data);
    $server->close($fd);
}

启动服务

$ php Index.php

确认onWorkerStart之前没有载入你要热更新的代码

public function onWorkerStart ($server, $worker_id)
{
    print_r(get_included_files());
    return;
}

关于守护我用的是supervisor

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
讨论数量: 10
gitxuzan

先赞后看

6年前 评论

引入index.php会输出laravel首页,楼主是怎么解决的?

6年前 评论

@过江的桥
因为我这边也是构建api..并未用到web路由,所以,我也就返回了一个字符串

Route::get('/', function () {
    return 'welcome';
});

不过这个问题确实存在~就目前来讲,我应该没有好的解决办法.. :sob: 很抱歉

6年前 评论

@罗阳 判断了下是否cli运行解决了:simple_smile:

6年前 评论
require __DIR__ . '/bootstrap/autoload.php';
$app = require_once __DIR__ . '/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$kernel->handle(
        Illuminate\Http\Request::capture()
);

这样不会输出首页,去掉了response

4年前 评论

请教下,我的业务逻辑是写在onmessage闭包里边,这种情况怎么热更新。

4年前 评论

docker的alpine系统,热加载没有反应,好像inotify在alpine不兼容

4年前 评论

学习了 inotify_init,inotify_add_watch,inotify_read。之前从来没用过

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!