ThinkPHP原生不支持长连接,必须引入GatewayWorker等外部服务层;其三进程架构(Gateway/BusinessWorker/Register)分离网络与业务逻辑,通过Events.php专注处理onConnect/onMessage/onClose事件,配合bindUid/sendToUid实现高效、安全的WebSocket即时通讯。

ThinkPHP 本身不支持长连接,做即时通讯必须引入外部服务层——不是靠改路由或加个控制器就能跑起来的。硬上 HTTP 轮询或 Server-Sent Events,延迟高、连接数扛不住、离线消息难保障,实际项目里基本不可用。
为什么不能只用 ThinkPHP 原生 HTTP?
HTTP 是无状态短连接,每次请求都要重建 TCP、走完整握手流程。聊天场景下:
– 每秒轮询一次,1000 用户就是 1000 并发请求,Apache/Nginx 很快打满
– 消息到达延迟至少 500ms~2s,用户感知明显卡顿
– 断网重连后无法自动恢复会话,已读/未读状态容易错乱
– onMessage、onClose 这类事件根本不存在,没法监听客户端真实在线状态
GatewayWorker 是最稳妥的落地选择
相比 Swoole 直接嵌入(需额外维护进程、处理守护、兼容性敏感),GatewayWorker 封装了业务逻辑与网络层分离的结构,对 ThinkPHP 项目侵入小,调试友好,且文档和社区案例足够多。
- 它由
Gateway、BusinessWorker、Register三部分组成,你只需专注写Events.php里的业务回调 - 绑定用户 ID 的操作是原子的:
Gateway::bindUid($client_id, $uid),后续发消息直接sendToUid($uid, $data) - 不依赖
think-swoole或自定义命令行,避免与 ThinkPHP 生命周期冲突(比如容器未初始化就调用模型) - 上线时用
php start.php start -d启动即可,日志、平滑重启、连接数监控都有现成接口
WebSocket 连接必须配对处理跨域和路径
前端用 new WebSocket('ws://your-domain.com:7272') 连不上?大概率是下面三个地方没对齐:
-
Gateway监听地址必须是外网可访问 IP,别写127.0.0.1—— 容器或云服务器内网 IP 对浏览器不可见 - Nginx/Apache 若做了反向代理,要显式开启 WebSocket 升级头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - ThinkPHP 路由和
GatewayWorker完全无关,别在route.php里配ws/路由——那是 HTTP 路由,WebSocket 不走这个链路
JWT 鉴权不能只靠 header 传 token
WebSocket 握手阶段不支持 Bearer Token 自动注入,前端必须手动把 token 拼进 URL:
立即学习“PHP免费学习笔记(深入)”;
const ws = new WebSocket(`ws://your-domain.com:7272?token=${localStorage.getItem('jwt')}`);
后端在 onConnect 里解析 $request->get['token'],再调用 JWT 解码验证。漏掉这步,任何人都能直连网关,等于裸奔。
更关键的是:验证失败必须立刻 Gateway::closeClient($client_id),否则无效连接会堆积,触发 max_connection 限制后新用户连不上。
GatewayWorker 的 onClose 回调里要不要删数据库在线表记录?用户切后台时前端该不该主动 close?这些细节没对齐,消息就一定不同步。



















