ThinkPHP本身不支持长连接,因其HTTP请求-响应生命周期结束后进程即销毁,无法维持WebSocket所需的持久socket连接;必须使用Workerman或GatewayWorker作为独立常驻服务,TP仅作业务逻辑桥接层。
thinkphp 本身不支持长连接,直接在控制器里启动 websocket 服务必然失败——http 生命周期一结束,进程就销毁,socket 根本留不住。必须用 workerman 或 gatewayworker 作为独立常驻服务,tp 只负责桥接业务逻辑。
为什么 new Worker('websocket://...') 写在 TP 控制器里会静默失败
PHP-FPM 或 CLI 环境下,控制器脚本执行完就退出,Worker::runAll() 根本没机会进入事件循环;即使强行跑起来,也会被系统回收,前端看到的是 Connection reset by peer 或握手 400 错误。这不是代码写错了,是运行模型冲突。
- TP 是请求驱动(request-response),Workerman 是常驻进程(event-loop)
-
onConnect/onMessage回调只有在Worker::runAll()进入主循环后才生效 - 所有 socket 管理、心跳、连接池都依赖 Workerman 的主进程调度,TP 容器无法接管
用 topthink/think-worker 启动 WebSocket 服务(适合轻量场景)
它把 Workerman 封装成 ThinkPHP 命令行指令,自动加载容器和配置,避免手动 require 和重复初始化。
- 安装:
composer require topthink/think-worker - 配置
config/worker.php:确保'protocol' => 'websocket'、'port' => 2346、'count' => 4 - 启动:
php think worker:server(不是worker:gateway) - 注意:该命令默认只启一个 Worker 进程,不带 Gateway/BusinessWorker 分离模型;如需广播、用户在线状态等能力,得切到 GatewayWorker
用 workerman/gateway-worker 实现连接与业务分离(推荐用于生产)
当你需要群发消息、维护用户在线列表、做客服系统这类强连接管理场景时,GatewayWorker 是更稳妥的选择——它把连接层(Gateway)和业务层(BusinessWorker)拆开,TP 只作为 BusinessWorker 的调用方。
- 安装:
composer require workerman/gateway-worker - 配置
config/gateway_worker.php,重点设'eventHandler' => 'app\logic\Events' - 在
app/logic/Events.php中处理onConnect/onMessage/onClose,调用 TP 模型前必须手动绑定容器:Container::getInstance()->bind('app', app()),否则Db::table()会返回null - 启动顺序:先启
php start.php start -d(Gateway),再启php start_businessworker.php start -d(BusinessWorker)
前端连不上 ws://localhost:2346 的三个高频卡点
90% 的连接失败都卡在这三处,而不是代码逻辑问题。
立即学习“PHP免费学习笔记(深入)”;
- 端口被占或防火墙拦截:
lsof -i :2346(macOS/Linux)或netstat -ano | findstr :2346(Windows)确认空闲;云服务器还要检查安全组是否放行 - 协议写错:HTTPS 页面必须用
wss://,写成ws://浏览器会静默拒绝(不报错、不提示) - 跨域未处理:Workerman 默认不校验 Origin,但前端若显式设置
headers: {Origin: 'https://xxx.com'},而服务端没透传或忽略,可能触发握手失败
真正难的不是写几行 onMessage,而是理解「常驻进程」和「请求生命周期」的边界在哪里。很多坑都源于试图让 TP 承担它不该管的事——比如自己维护连接列表、手动 close socket、在控制器里 new Worker。把这些交给 Workerman,TP 只管查数据库、发通知、写日志,事情就清楚多了。



















