不能在thinkphp控制器中new websocket,因http请求短生命周期导致连接秒断;必须用独立常驻进程(如php think swoole:server)启动,并配置swoole.php为websocket模式、注册监听器、在监听器中处理消息及连接状态校验。

ThinkPHP 消息推送不能靠“控制器里 new WebSocket”硬上,否则连接秒断、消息必丢——这是最常被踩的坑。
为什么不能在 HTTP 控制器里启动 WebSocket 服务
ThinkPHP 的 HTTP 请求是短生命周期:请求进来 → 执行完 → 进程销毁。而 WebSocket 必须长期驻留、监听端口、维护连接池。new Swoole\WebSocket\Server 放在 index/index 这类控制器里,响应一结束,整个 Server 实例就被 GC 回收,客户端立刻收到 net::ERR_CONNECTION_REFUSED。
- 错误现象:
WebSocket connection to 'ws://...' failed - 真实后果:用户登录后刚点开聊天页,消息就收不到;重连逻辑再完善也救不回来
- 正确路径:必须用独立常驻进程,例如通过
php think swoole:server或单独脚本php start_websocket.php启动
用 think-swoole 注册 WebSocket 广播事件的硬性条件
装了 topthink/think-swoole 不等于 WebSocket 就能跑起来。三个配置项缺一不可:
-
config/swoole.php中必须设'type' => 'websocket',否则走的是 HTTP 模式 - 必须显式注册监听器类,例如
'handler' => \app\listener\WebSocketListener::class - 业务逻辑不能写在控制器里,得在
app/listener/WebSocketListener.php中实现onMessage,并手动遍历$server->connections发送
漏掉任一环,onOpen 可能触发,但 onMessage 根本不会进,前端发消息石沉大海。
广播前必须校验的三个连接状态
90% 的“消息没收到”不是代码写错,而是连接状态没兜住:
- 心跳失效:Swoole 默认 60 秒无 ping/pong 就断连,得在
onOpen里补上$server->set(['heartbeat_idle_time'=>300, 'heartbeat_check_interval'=>60]) - fd 冲突:用户重复登录时,旧连接未下线,新
$fd覆盖 Redis 中的映射,导致广播发到已断开的 fd 上;必须用Redis存user_id → [fd1, fd2],登录前先PUBLISH下线通知 - 遍历时跳过无效 fd:每次
send()前必须调用$server->isEstablished($fd),否则会触发Invalid fd报错并中断当前循环
SSE 方案更适合只推不收的站内通知
如果只是发系统公告、审核结果这类单向通知,别硬上 WebSocket。ThinkPHP6 原生支持流响应,几行代码就能跑通 SSE:
- 控制器里设好
text/event-stream头,用ob_flush()+flush()推送 - 前端用
EventSource监听,自动重连,兼容性比 WebSocket 更稳 - 关键要绑定用户身份:URL 带
?token=xxx,服务端校验后才允许建立连接,避免未授权接收通知
真正难的从来不是写推送逻辑,而是连接怎么不丢、状态怎么不乱、用户重复登录时 fd 怎么清理——这些细节不在文档首页,但线上一出问题,全卡在这儿。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











