yii框架不内置websocket,必须依赖swoole/workerman等外部服务;因http请求短生命周期与websocket长连接异步模型冲突,直接在控制器启动会导致阻塞、组件不可用及进程泄漏。

Yii 框架本身不内置 WebSocket 服务器能力,必须靠外部服务(如 Swoole / Workerman)承接连接,再与 Yii 应用协同完成消息路由和业务逻辑。直接在控制器里写 new Swoole\WebSocket\Server 或调用 $app->run() 启动长连接,会导致请求阻塞、进程泄漏、无法复用 Yii 组件。
为什么不能在 Yii Web 请求中启动 WebSocket 服务
Web 请求生命周期是短时、同步、无状态的;WebSocket 是长连接、异步、需维持上下文的。两者模型冲突:
-
Swoole\WebSocket\Server::start()会阻塞当前 PHP 进程,HTTP 响应永远不返回,Nginx/Apache 最终超时断连 - Yii 的
Application实例在请求结束时自动销毁,你手动 new 出来的 Server 对象无法访问 DB、Model、Cache 等组件 - 若强行在
actionIndex()里调用listen(),日志里大概率出现PHP Warning: Swoole\Server::start(): event loop already running
推荐架构:Swoole 作为独立服务 + Yii 提供 HTTP API 推送入口
让 Swoole 进程常驻监听 WebSocket 连接,Yii 只负责处理用户行为(如发消息、改订单),再通过 HTTP 或 IPC 通知 Swoole 服务广播。这是最稳定、可运维、易调试的模式。
- Swoole 服务单独部署,例如监听
0.0.0.0:9502,使用onOpen/onMessage管理连接和收发 - Yii 控制器中不碰 socket,只提供一个推送接口:
POST /api/v1/push,接收{to: 'user_123', event: 'order_update', data: {...}} - Swoole 服务内维护一个连接映射表(如
$connections[user_id] = $fd),收到 Yii 的推送请求后,查表并$server->push($fd, $json) - 避免用 Redis Pub/Sub 中转——它增加延迟和失败点;直连更可控(Swoole 支持
Http/Client同步调用 Yii 接口)
如何在 Swoole 进程里安全复用 Yii 的 Model 和 DB
不能直接 require vendor/autoload.php 后 new yii\web\Application,那会重复初始化、抢端口、爆内存。正确做法是按需加载组件:
- 在 Swoole 的
onWorkerStart回调中,仅初始化你需要的 Yii 组件,例如:Yii::createObject(['class' => 'yii\db\Connection', ...]) - 数据库连接要设
'charset' => 'utf8mb4'并开启'enableSchemaCache' => true,否则每个 worker 都建新连接 - 不要在
onMessage里 new Model,改用静态方法或工厂类封装查询逻辑,防止模型事件监听器堆积 - 如果必须用 ActiveRecord,确保
__destruct()不触发未关闭的事务——Swoole worker 不走 Yii 请求销毁流程
前端连接和鉴权容易被忽略的关键点
WebSocket 连接不是“打开就完事”,没做鉴权等于把后台数据库裸奔暴露:
- 不要让前端直接连
ws://your-domain.com:9502,必须带 token 参数:ws://your-domain.com:9502?token=xxx - Swoole 的
onOpen里立刻校验 token(查 Redis 或调用 Yii 的/api/v1/verify),验证失败直接$server->close($fd) - token 必须一次性使用或限时(如 30 秒),防止重放;不要存 session_id 或 cookie,WebSocket 不传这些
- 前端
WebSocket实例需监听onerror和onclose,自动重连时加退避(1s → 2s → 4s),别暴力轮询
真正卡住项目的从来不是“怎么连上”,而是连接生命周期管理、消息幂等性、worker 进程崩溃恢复、以及 Yii 和 Swoole 之间那层薄薄的依赖边界——越想绕开它,越容易掉坑里。











