websocket空闲断连主因是链路层超时与心跳未对齐,swoole通过heartbeat_idle_time和heartbeat_check_interval配合实现服务端保活,但需同步调大nginx proxy_read_timeout,并要求客户端主动发ping/pong,断连后应按code分流+指数退避重连。

WebSocket空闲连接自动断开,根本原因不是Swoole本身不稳定,而是链路中某一层设置了空闲超时,而心跳没对齐这个阈值。Swoole 4.x 的 WebSocket Server 提供了原生、轻量、高效的心跳检测机制,只需两个关键参数就能让服务端主动管理连接生命周期。
核心配置:heartbeat_idle_time 和 heartbeat_check_interval
这两个参数必须配合使用,且数值关系直接影响保活效果:
- heartbeat_idle_time:单位秒,表示一个连接允许的最大空闲时间。超过该时长未收发任何数据(包括业务消息和协议 Ping),Swoole 就判定为“失效连接”
- heartbeat_check_interval:单位秒,表示服务器每间隔多久扫描一次所有连接,检查是否超 idle 时间
推荐设置(适用于大多数代理+云环境):
$server->set([
'heartbeat_idle_time' => 60, // 最大空闲 60 秒
'heartbeat_check_interval'=> 30, // 每 30 秒检查一次
]);
注意:heartbeat_check_interval 必须小于 heartbeat_idle_time,否则检查永远赶不上超时——比如设成 60 和 60,就等于等连接断了才去查。
为什么只配 Swoole 还不够?Nginx 层必须同步调大
即使 Swoole 设置了 60 秒空闲不关,若前面有 Nginx 反向代理,默认 proxy_read_timeout 60 会在第 60 秒静默切断 TCP 连接,且不通知 Swoole,导致你看到的错误是 1006(无 close 帧),日志里也找不到 onClose 记录。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
必须在 Nginx 的 location 块中显式覆盖:
location /ws/ {
proxy_pass http://127.0.0.1:9501;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 关键!至少大于 Swoole 的 heartbeat_idle_time
proxy_send_timeout 86400;
proxy_buffering off;
proxy_cache off;
}
客户端也要配合:别只等服务端 Ping
Swoole 的心跳是服务端单向检测(它只看有没有收到数据,不区分 ping 还是业务消息),但实际部署中,中间设备(如四叶天代理、运营商 NAT、ALB)往往只认标准 WebSocket Ping/Pong 控制帧。
因此建议客户端也主动发 Ping,并监听 Pong 回应:
- 浏览器环境:可使用
ws.send(JSON.stringify({type:'ping'}))+ 自定义 pong 响应逻辑(因浏览器不暴露原生 Ping API) - Node.js(ws 库):
pingInterval: 25000, pingTimeout: 5000,确保 25 秒发一次,5 秒内没回就判定异常 - 移动端或小程序:用定时器模拟心跳消息,payload 尽量小(如
{"h":1})
服务端收到后应立即回一个简单 pong 消息(如 {"h":0}),避免业务逻辑阻塞响应。
断连后重连不能硬刚
心跳只是防断,断了还得稳重连。直接 onclose 就 setTimeout 重连,容易触发状态竞争或雪崩:
- 用全局变量缓存当前 ws 实例,新建前先
wsRef?.close() - 加布尔锁
isReconnecting,防止多次触发 - 按 code 分流:code === 1000(正常关闭)不重连;code === 1006 或 4xxx(异常/凭证过期)才走重连流程
- 采用指数退避:1s → 2s → 4s → 8s… 上限建议 30s,总尝试不超过 8 次










