推荐使用 think-swoole(v4.1+),因其兼容 TP6 生命周期、支持 WebSocket 原生事件与中间件;workerman 扩展(如 think-worker)仅适合 HTTP 长轮询,无法真正支持 WebSocket。

WebSocket 服务启动失败:think-swoole 和 workerman 到底选哪个
TP6 官方不内置 WebSocket 服务,必须靠扩展。直接用 workerman 扩展(如 topthink/think-worker)容易卡在握手阶段或无法接收消息,根本原因是 TP6 的 HTTP 生命周期和 WebSocket 长连接模型冲突——请求一来就走完整中间件链、响应完就关连接,根本没留给 WebSocket 升级和维持的机会。
真正可行的是 think-swoole(推荐 v4.1+),它把 Swoole 的 WebSocket\Server 封装进 TP6 的命令生命周期里,能正确处理 onOpen/onMessage/onClose,且支持中间件注入、容器绑定、日志写入等 TP 原生能力。
- 别装
topthink/think-worker,它只适合 HTTP 长轮询模拟,不是真 WebSocket -
think-swoole要求 PHP ≥ 7.4、Swoole ≥ 4.8,且必须关闭opcache.enable_cli=0,否则热更新失效 - 启动命令是
php think swoole,不是php think worker:server
onMessage 收不到客户端消息:TP6 的事件回调怎么注册才生效
很多人把业务逻辑直接写在 Swoole\WebSocket\Server 的 onMessage 回调里,结果发现 $server->push() 没问题,但 onMessage 根本不触发——其实是没配对监听器。TP6 的 think-swoole 把原生事件转成了事件系统,必须显式绑定。
在 app/event.php 中加这一行:
立即学习“PHP免费学习笔记(深入)”;
return [
'swoole.websocket.message' => [\App\Listener\WebSocketMessage::class],
];
然后创建 app/Listener/WebSocketMessage.php,确保它实现 think\swoole\contract\ListenerInterface,并在 handle 方法里处理 $event->frame(不是原生 $frame 对象):
-
$event->frame->fd是客户端唯一 ID,可用于$server->push() - 别在
handle里做耗时操作(如 DB 查询),会阻塞整个 WebSocket 进程;要用go()或投递到 task 进程 - 如果用 Redis 存在线用户,记得用
fd作 key,别用 session_id —— WebSocket 连接没有 session
如何让 WebSocket 和 TP6 的路由/验证/Token 体系打通
WebSocket 连接建立时(onOpen)只能拿到 $request->get 和 $request->header,拿不到 Cookie 或已解析的 Token。想复用 TP6 的 Auth 或 JWT 验证,得手动解析。
常见做法是在 URL 上带参数传 Token:ws://localhost:9501?token=xxx,然后在 swoole.websocket.open 监听器里取:
$token = $event->request->get['token'] ?? '';
if (!$token || !$this->validateToken($token)) {
$event->server->close($event->fd);
return;
}
注意几个关键点:
-
validateToken必须是无状态校验(比如 JWT 签名校验),不能查数据库或 Redis ——onOpen是同步阻塞的,慢了会导致握手超时 - 验证通过后,把用户 ID 写进
$server->set的user_id属性,后续onMessage可通过$server->connections[$fd]['user_id']拿到 - 别试图在 WebSocket 里调
Auth::id(),TP6 的 Auth 组件依赖 HTTP 请求上下文,这里不存在
连接断开后用户状态没清理:onClose 为什么有时不执行
浏览器关标签、网络中断、心跳超时都会触发 onClose,但实测中常出现连接没了,onClose 却没进监听器——本质是 Swoole 的连接回收机制和 TP6 事件监听时机错位。
根本解法是启用 Swoole 的 heartbeat_check_interval 并配合主动探测:
- 在
config/swoole.php中设置:'heartbeat_check_interval' => 60,'heartbeat_idle_time' => 600 - 在
swoole.websocket.close监听器里,必须显式删 Redis 中的fd映射和用户在线状态,不能只删内存数组 - 如果用了 Gateway 模式(多 Worker),
onClose只在当前 Worker 触发,需用Redis pub/sub或Table同步状态
最易忽略的一点:Swoole 的 onClose 不保证 100% 可达,网络闪断时可能收不到。生产环境必须加「最后活跃时间」兜底清理逻辑,不能只信事件回调。



















