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
{
"route": "site/test",
"message": "这是一条来自websocket客户端的消息"
}
// 这里展示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));
}
}
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']);
}
}