webman默认不启用心跳和重连机制,必须手动实现客户端定时发ping、服务端onmessage中调用$connection->pong()响应,并配合nginx proxy_read_timeout≥300秒及前端指数退避重连,否则连接易被中间设备静默断开。

Webman 默认不自动心跳,也不内置重连逻辑——必须手动实现 ping/pong 交互 + 外部重连控制,否则连接在 30~60 秒内大概率被 Nginx 或运营商 NAT 断开。
为什么 Webman 的 WebSocket 连接容易静默断开
Webman 基于 Workerman,底层用的是 PHP 的 socket,它本身不主动发 Ping 帧,也不监听 Pong。浏览器或移动端客户端发来的 ping 帧默认被忽略(除非显式处理),服务端更不会定时推 ping。结果就是:只要业务消息间隔超过中间设备超时阈值(如 Nginx proxy_read_timeout=60、阿里云 SLB 默认 900 秒但实测常为 300 秒),连接就被无声切断,错误码通常是 1001(going away)或直接 EOF。
常见现象包括:
- 用户切到后台再切回,页面卡住无响应
- 日志里没报错,但
onClose突然触发,code是1001或0 - 使用
ws://本地调试正常,一上生产就频繁断连
手动实现服务端心跳响应(ping/pong)
Webman 的 WebSocket Server 不自动响应 Ping 帧,必须在 onMessage 中拦截并返回 Pong。注意:不能只靠 Workerman 的 $connection->send() 发 pong 字符串,必须用 WebSocket 协议帧格式——好在 Webman 封装了 $connection->pong() 方法,直接调用即可。
关键点:
-
onMessage回调里,先判断$data是否为''且$connection->isWebSocket()为 true —— 这是标准 Ping 帧的特征(空 payload) - 不要对空消息做业务逻辑,立刻
$connection->pong() - 别用
$connection->send('pong'),那是普通文本,不是协议级 Pong 帧,客户端收不到确认
示例代码片段:
use Webman\Connection\ConnectionInterface;
<p>// 在 websocket.php 的 onMessage 中
'onMessage' => function (ConnectionInterface $connection, $data) {
if ($connection->isWebSocket() && $data === '') {
$connection->pong(); // 必须用这个,不是 send()
return;
}
// 其他业务消息处理...
}</p>
客户端需主动发心跳,服务端不能“代劳”
Webman 没有类似 websockets 库的 ping_interval 参数,也无法在服务端定时向每个连接发 Ping(Workerman 的 timer 是全局的,且 WebSocket 连接对象在 timer 回调中不可靠)。所以心跳发起方必须是客户端——这是最稳妥、兼容性最好的做法。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
客户端应:
- 建立连接后,启动
setInterval(() => ws.send(''), 25000)(间隔建议 ≤ 中间设备超时 - 10 秒,比如 Nginx 设了 60 秒,这里设 45 秒) - 监听
onclose,触发时清掉定时器,避免内存泄漏 - 不要依赖
onerror捕获断连,它不总是触发;以onclose为准
服务端无需反向心跳,否则会增加复杂度且易出竞态(比如连接已关闭但 timer 还在尝试发 ping)。
重连必须带退避 + 状态隔离
Webman 服务端不管理重连,这部分完全由客户端承担。但常见错误是:断开后立即重试,导致大量请求打到刚重启的服务端,或与旧连接残留句柄冲突。
安全做法:
- 首次重连延迟 1 秒,之后每次翻倍:
Math.min(30000, 1000 * Math.pow(2, attempt)) - 加入随机抖动(±20%),避免“惊群效应”:
delay * (0.8 + Math.random() * 0.4) - 每次重连前,检查旧
WebSocket实例是否已关闭(ws.readyState !== WebSocket.CLOSED),并手动ws.close() - 重试上限设为 5 次,超限后应降级(如提示“网络不稳定”,停发实时数据)
特别注意:Webman 的 Worker 进程是常驻的,但单个 WebSocket 连接断开后,其资源(如绑定的闭包、数据库连接)不会自动释放。若业务中用了 $connection->setReadOnly() 或自定义属性,重连时要确保新连接重新初始化,避免状态污染。
真正难的不是写几行 pong(),而是把心跳间隔、Nginx 超时、客户端退避、连接清理这四者对齐——漏掉任意一环,连接都会在你以为稳了的时候突然消失。










