jcbowen/yiiswoole

Yii websocket server component

Installs: 49

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 1

Forks: 1

Open Issues: 0

Type:yii2-extension

v4.1.7 2024-03-28 09:53 UTC

README

介绍

Yii2 + Swoole4

目前实现了websocket服务器,同时支持了高性能共享内存Table的使用

不兼容说明

使用了usleep,所以不兼容win环境

前提

服务器中需要安装swoole4

(如果是宝塔搭建的环境,直接在使用的php版本中安装swoole4扩展)

由于某些跟踪调试的 PHP 扩展大量使用了全局变量,可能会导致 Swoole 协程发生崩溃。请关闭以下相关扩展:

xdebug、phptrace、aop、molten、xhprof、phalcon(Swoole 协程无法运行在 phalcon 框架中)

安装教程

composer执行

composer require "jcbowen/yiiswoole"

或者在 composer.json 加入

"jcbowen/yiiswoole": "^4.0"

配置说明

console/config/main.php的controllerMap中加入配置

        'websocket' => [
            'class'        => \Jcbowen\yiiswoole\websocket\console\controllers\WebSocketController::class,
            'serverClass'  => \Jcbowen\yiiswoole\websocket\console\components\Server::class, // 可不填,默认值
            'serverConfig' => [
                'daemonize'                => true,// 守护进程执行
                'heartbeat_check_interval' => 60, // 启用心跳检测,默认为false
                'heartbeat_idle_time'      => 120, // 连接最大允许空闲的时间,启用心跳检测的情况下,如未设置,默认未心跳检测的两倍
                'pid_file'                 => '@runtime/yiiswoole/websocket.pid',
                'log_file'                 => '@runtime/yiiswoole/websocket.log',
                'log_level'                => SWOOLE_LOG_ERROR,
                'buffer_output_size'       => 2 * 1024 * 1024, //配置发送输出缓存区内存尺寸
                'worker_num'               => 1,
                'max_wait_time'            => 60,
                'reload_async'             => true,
            ],
            'serverPorts'  => [
                // 第一个为websocket主服务
                'ws' => [
                    'host' => '0.0.0.0',
                    'port' => 9408,
                    'cert' => false, // 证书类型 默认值:false 其它值:'ssl'
                ]
            ],
            'tablesConfig' => [],
        ],

使用

# 启动 
php yii websocket/start
# 停止 
php yii websocket/stop
# 重启 
php yii websocket/restart

运行说明

websocket客户端向服务器发送json字符串,如:
如果握手的时候,携带了目录路径,该路径将会作为route缓存起来;请求中如果携带了route字段,则替换缓存中的route
{
  "route": "site/test",
  "message": "这是一条来自websocket客户端的消息"
}
通过执行\Jcbowen\yiiswoole\components\ContactData::get($fd, '_B');方法,可以读取上下文中缓存的信息;
通过执行\Jcbowen\yiiswoole\components\ContactData::get($fd,'_GPC');方法,可以读取上下文中缓存的get数据;
其中server和frame会被缓存到_B中;
接收到的json会被转为数组后缓存到_GPC中;
携带的目录代表的是监听到动作后转发到哪个路由(由于通过console运行的进程,所以这里的路由指的是console里的路由)。
    // 这里展示onMessage的源码,用来理解实现原理
    public function onMessage(WsServer $server, Frame $frame)
    {
        $_B   = ContactData::get($frame->fd, '_B');
        $_GPC = ContactData::get($frame->fd, '_GPC');

        // 修改上下文中的信息
        $_B['WebSocket']['on'] = 'message';

        $jsonData = Util::isJson($frame->data) ? (array)@json_decode($frame->data, true) : $frame->data;
        $jsonData = $jsonData ?: $frame->data; // 避免因json解析失败导致数据丢失的情况

        // 空数据为触发心跳
        if (empty($jsonData))
            return $server->push($frame->fd, json_encode([
                'errcode' => ErrCode::SUCCESS,
                'errmsg'  => 'Heart Success'
            ], JSON_UNESCAPED_UNICODE));

        if (is_array($jsonData)) {
            $_GPC  = ArrayHelper::merge((array)$_GPC, $jsonData);
            $route = trim($_GPC['route']) ?: '';

            // 如果route不存在,不知道应该由哪个路由进行处理,只能进行报错处理
            if (empty($route))
                return $server->push($frame->fd, json_encode([
                    'errcode' => ErrCode::PARAMETER_ERROR,
                    'errmsg'  => 'Empty Route',
                ], JSON_UNESCAPED_UNICODE));

            // 更新上下文中的信息
            ContactData::set($frame->fd, '_B', $_B);
            ContactData::set($frame->fd, '_GPC', $_GPC);

            // 根据json数据中的路由转发到控制器内进行处理
            try {
                return Yii::$app->runAction($route, [$server, $frame, $frame->fd, $this]);
            } catch (Exception $e) {
                Yii::info($e);
                $this->Controller->stdout($e->getMessage() . PHP_EOL, BaseConsole::FG_RED);
                return false;
            }
        } else {
            // 数据格式错误
            return $server->push($frame->fd, json_encode([
                'errcode' => ErrCode::ILLEGAL_FORMAT,
                'errmsg'  => 'Data Format Error',
            ], JSON_UNESCAPED_UNICODE));
        }
    }
总结:jcbowen/yiiswoole插件会在websocket触发onmessage时,根据route调用对应的控制器方法,并将websocket服务和接收到数据分别存放到_B_GPC中;

在控制器方法中使用

class SiteController extends Controller
{    
    use Jcbowen\yiiswoole\websocket\console\components\Server;
    use Swoole\WebSocket\Frame;
    use Swoole\WebSocket\server as WsServer;

    public function actionTest(WsServer $WsServer, Frame $frame,Server $server)
    {
        $_B = ContactData::get($frame->fd, '_B');
        $_GPC = ContactData::get($frame->fd, '_GPC');
        
        /** @var \Swoole\Table $table */
        $table = $server->_tables['test_table'];
        
        return $WsServer->push($frame->fd, $_GPC['message']);
    }
}