workerman的websocket心跳必须手动实现,服务端需在onmessage中用$connection->pong()响应空ping帧,并无条件更新lastmessagetime;客户端须主动发心跳并指数退避重连,否则连接易被nginx或nat在60秒内静默断开。

Workerman 的 WebSocket 心跳必须手动实现,没有开箱即用的 ping_interval 或自动响应机制——靠默认行为,连接大概率在 60 秒内被 Nginx、SLB 或运营商 NAT 静默断开,错误码常为 1001 或直接 EOF。
onMessage 中必须拦截空字符串并调用 $connection->pong()
Webman/Workerman 不会自动响应 Ping 帧。浏览器或客户端发来的标准 Ping 帧 payload 为空(''),服务端若不显式处理,该帧就被忽略,无法触发协议级 Pong 响应。
- 只判断
$data === ''不够,要加$connection->isWebSocket()双重校验,避免误伤非 WebSocket 连接 - 绝对不要用
$connection->send('pong')—— 这是普通文本,不是 WebSocket 控制帧,客户端收不到确认 - 业务逻辑前必须先处理心跳,否则 JSON 解析失败导致
return,时间戳没更新,后续会被误判离线
示例:
'onMessage' => function(ConnectionInterface $connection, $data) {
if ($connection->isWebSocket() && $data === '') {
$connection->pong(); // 关键:必须用这个
return;
}
// 后续才是你的业务解析和处理
}
每个连接必须无条件更新 lastMessageTime,且首次进 onMessage 就做
很多人只在成功 decode JSON 后才写 $connection->lastMessageTime = time(),结果客户端发的 {"type":"ping"} 因字段缺失或格式错误解析失败,直接 return,时间戳完全没更新——30 秒后就被定时器当“死连接”关掉。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
-
onMessage第一行就该是$connection->lastMessageTime = time(),不管$data是啥、能不能 parse - 未调用
$connection->bindUid()的匿名连接也得初始化,否则遍历时触发 PHP Notice - 别依赖 JSON 字段判断心跳,容易被畸形数据绕过;推荐用二进制前缀(如
\x01)或子协议区分
服务端超时检查必须用全局定时器 + 最小堆,不能靠 tcp_keepalive
Linux 内核的 net.ipv4.tcp_keepalive_time=7200 在移动网络和云环境基本无效。运营商 NAT、4G 基站防火墙普遍在 60~120 秒回收空闲连接,所以服务端检测阈值必须严控在最短超时之下。
- 推荐心跳间隔 ≤45 秒(客户端发),服务端超时设为 ≤90 秒(容忍一次丢包)
- 定时器用
Timer::add(55, function() { ... })每 55 秒扫一次,比超时阈值更频繁,避免漏检 - 遍历
$worker->connections时,对每个连接检查time() - $connection->lastMessageTime > 90,超了就$connection->close() - 底层 Timer 基于最小堆,插入/检查都是
O(log N),撑万级连接没问题;别自己手写轮询或用sleep()
前端发心跳不能用 setInterval 硬轮询,要绑定状态+防冻结
页面切后台时,Chrome 会节流甚至暂停 setInterval,导致心跳堆积或中断;重连后不清旧定时器还会引发多实例并发发送。
- 心跳发送必须绑定在
ws.readyState === WebSocket.OPEN状态下,每次发前clearTimeout(pongTimeout) - 用
setTimeout单次等待服务端{type:"pong"}响应,10 秒没到就ws.close() - 监听
document.addEventListener('visibilitychange', ...),页面不可见时暂停心跳,切回前台再恢复 - 首次断连立即重试,连续失败 2 次起启用指数退避(3s → 8s → 20s),并加 ±1s 抖动防雪崩
最难的部分不是写那几行 pong() 或定时器,而是把 lastMessageTime 更新逻辑塞进每一个可能进入 onMessage 的入口,以及让前端重连策略真正理解模组注册、SIM 附着这些底层延迟——比如 4G 模组从开机到能建 WS 连接,可能要等 +CGATT: 1 和 +CREG: 1,1 两个 AT 响应,中间空跑重连全是徒劳。









