webman 不能在控制器写 websocket 逻辑,因 http worker 与 websocket 生命周期不匹配,连接无法注册和维持;websocket 必须独立进程运行,通过 channel 跨进程通信,手动管理连接、心跳与内存清理。

Webman 为什么不能在控制器里写 WebSocket 逻辑
因为 Webman 的 HTTP Worker 和 WebSocket 连接生命周期完全不匹配。HTTP 请求一结束,Worker 就会回收所有临时资源,onMessage 回调根本不会被注册,更别说维持长连接了。
常见错误现象是:前端 new WebSocket('ws://...') 后立刻断开,浏览器控制台显示 WebSocket connection to 'ws://...' failed,但服务端日志一片空白——连接压根没进 WebSocket 进程。
- WebSocket 必须跑在独立进程里,通过
config/process.php注册,协议必须是websocket://0.0.0.0:2233这类形式 - 进程类必须继承
support\Process,不是Workerman\Worker;否则onWorkerStart不触发,ps aux看不到子进程 - 连接建立后,所有数据收发、心跳、状态维护都得手动写,Webman 不提供
$connection->uid或$session->get()这类封装
如何让 HTTP 接口能主动推消息给指定用户
关键不在“发不发得出”,而在“怎么跨进程找到那个连接”。HTTP Worker 和 WebSocket 进程内存隔离,不能直接调用 $connection->send(),强行共享变量在线上多实例部署时必然失效。
正确路径是走 Channel 做消息中转:
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- WebSocket 进程启动时,用
Channel::getInstance()->subscribe('push_topic', $callback)订阅主题 - HTTP 接口里调用
Channel::getInstance()->publish('push_topic', ['uid' => 1001, 'data' => [...]]) -
$callback中根据payload['uid']查找事先存好的连接映射表(如static $clients = [];),再调用$connection->send() - 务必在
onClose回调里从映射表中unset对应连接,否则内存泄漏,php start.php status会看到 RSS 持续上涨
WebSocket 连接保活的三个硬性动作
没有心跳、超时检测、重连策略的 WebSocket,上线三天内必掉线率过半。浏览器页面失焦、手机锁屏、NAT 超时都会无声切断连接,不能依赖服务端单方面 keepalive。
- 心跳必须由客户端主动发
ping(比如每 25 秒ws.send(JSON.stringify({type:'ping'}))),不能只等服务端pong - 客户端需维护
lastPongTime,用setTimeout检查:若Date.now() - lastPongTime > 45000,立即close()并触发重连 - 重连必须指数退避,且由 Dedicated Worker 全权控制;主线程只负责转发消息,不参与保活逻辑——否则页面后台时定时器被节流,心跳就停了
- 旧版 Safari(如 15.x)不支持 Worker 内建 WebSocket,需在 Worker 启动时
try { new WebSocket('') }探测,失败则降级为主线程 +visibilitychange补心跳
别碰 server->push() 和 webman/push 插件的群发功能
这两个看似方便,实则是中小规模下的“温柔陷阱”。5 万在线用户一次 server->push(),会同步遍历所有 session、逐个序列化、逐个 write,CPU 瞬间拉满,worker 卡死 3 秒以上,新连接全被拒绝。
真实业务里,99% 的推送都是点对点或小范围分片,不是广播:
- 禁用全局广播,改用读扩散:消息只写一次到
Redis Streams或 Kafka,客户端按游标拉取 - 实时触达走轻量网关:Webman 只推给 Go/Node 编写的在线状态服务,由它查路由表、异步下发
- 临时应急可用
server->getClientInfo()分片循环,但单次最多处理 10000 个连接,并加usleep(1000)防抖 -
webman/push插件的私有频道鉴权接口/plugin/webman/push/auth必须返回 200 + JSON,否则前端subscribe()静默失败,压测时要用ab -n 10000 -c 500测它
真正难的从来不是“连上”,而是“连得稳、推得准、不爆内存”。连接映射表清理、Channel 配置一致性、Worker 内心跳自主性——这些细节漏掉一个,线上就是凌晨三点的告警电话。










