Webman需通过独立WebSocket进程+Channel跨进程通信实现HTTP主动推送:在config/process.php注册ws进程,用Channel订阅/发布消息,连接时绑定uid并存Redis映射,配合心跳与重连保障长连接稳定。

Webman 本身不内置 WebSocket 服务,但通过 workerman 底层和进程管理能力,可以稳定承载 WebSocket 长连接。关键不在“能不能”,而在于「如何让 HTTP 接口能主动触发向指定 WebSocket 客户端发消息」——这是实时推送最常卡住的地方。
WebSocket 进程必须独立启动,不能混在 HTTP Worker 里
Webman 的 HTTP 请求由 Worker 处理,而 WebSocket 是长连接,生命周期远超一次 HTTP 请求。若把 WebSocket 逻辑写进控制器或中间件,连接会在请求结束时被回收,根本连不上。
- 必须在
config/process.php中注册一个独立的websocket进程,监听ws://协议地址(如websocket://0.0.0.0:2233) - 该进程类需继承
Workerman\Connection\TcpConnection或直接使用Workerman\Protocols\Websocket - 不要在
onMessage里做耗时操作(如查 DB、调 API),否则会阻塞整个 WebSocket 进程
HTTP 接口要推消息,得靠 Channel 进程通信
HTTP Worker 和 WebSocket 进程是隔离的两个进程空间,无法直接调用对方变量或方法。强行用全局变量或文件共享,会在线上多实例部署时彻底失效。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 必须启用
Channel组件(Webman 内置,基于 Redis 或内存实现)作为跨进程消息总线 - WebSocket 进程启动时,用
Channel::getInstance()->subscribe('push_topic', ...)订阅一个主题 - HTTP 接口逻辑中,用
Channel::getInstance()->publish('push_topic', $payload)发布消息 - 注意:订阅和发布必须用同一套 Channel 配置(如都走 Redis),否则 publish 后没人收到
客户端连接必须带唯一标识,否则没法精准推送
广播给所有人($connection->send())容易,但真实业务需要「推给张三」「推给订单 ID=123 的买家」——这就要求每个 WebSocket 连接能绑定业务身份。
- 前端建立连接时,把用户 ID 或设备 ID 拼在 URL 上,例如:
ws://127.0.0.1:2233?uid=1001 - WebSocket 进程的
onWebSocketConnect回调中,从$http_buffer解析出uid,并存入全局连接映射表(如static $clients = [];) - Channel 收到推送后,在回调里根据 payload 中的
target_uid查找对应$connection,再调用$connection->send() - 别忘了在
onClose里从映射表中移除连接,否则内存泄漏
心跳和重连不是可选项,而是连接存活的前提
浏览器标签页切走、网络抖动、NAT 超时都会导致连接静默断开。没有心跳机制,服务端几天都发现不了客户端已掉线,还往空连接发消息,最终触发 Connection reset by peer 错误。
- 前端每 30 秒发一次
ping帧(或自定义{"type":"ping"}) - WebSocket 进程在
onMessage中识别 ping 并立即回pong,同时刷新连接活跃时间 - 用
Workerman\Timer::add(60, function(){...})定期遍历所有连接,对超时(如 90 秒无 ping)的连接主动close - 前端 JS 必须实现断线自动重连逻辑,且首次重连延迟应指数退避(如 1s → 2s → 4s)
最容易被忽略的是 Channel 的序列化方式和连接映射表的进程可见性:Redis Channel 天然支持多机,但内存版 Channel 只在单机多 Worker 下有效;而 static $clients 在多进程模型下各 Worker 一份,彼此不可见——这意味着你必须用 Redis Hash 或数据库来存连接关系,或者干脆只用单个 WebSocket 进程(count => 1)。

















