WebSocket自动断开是多环节超时叠加所致,需同步调大Nginx的proxy_read_timeout、proxy_send_timeout、keepalive_timeout至86400并置于location块内,客户端心跳间隔须比最短中间设备超时小至少5秒,且服务端ping与客户端pong必须双向协同响应。

WebSocket自动断开不是“随机故障”,而是超时机制在多个环节叠加触发的结果;单纯调大某一个 timeout 值,大概率无效。
proxy_read_timeout 不是唯一决定因素
Nginx 默认 proxy_read_timeout 60s 确实是最常见的断连元凶,但只改它远远不够。这个参数控制的是 Nginx 等待后端返回数据的最长时间——如果后端 60 秒内没发任何帧(包括 pong),Nginx 就会主动 FIN 断开连接。可问题在于:
- 服务端可能本身设置了更短的空闲超时(如 Go 的
conn.SetReadDeadline设为 45s) - 中间云 LB、企业防火墙、运营商 NAT 网关普遍有 30–90 秒静默超时,它们根本不看 Nginx 配置
-
proxy_send_timeout和keepalive_timeout若未同步调大,也会在不同路径上掐断连接
实操建议:在 WebSocket 对应的 location 块中必须同时设置:
proxy_read_timeout 86400; proxy_send_timeout 86400; keepalive_timeout 86400;
注意:这些值不能写在 http 或 server 级,必须落在具体 location /ws/ { ... } 内才生效。
心跳间隔必须比最短中间设备超时小至少 5 秒
客户端发 ping 不是为了“讨好”服务端,而是为了穿透所有中间链路。iOS WKWebView 后台挂起、阿里云 SLB、AWS ALB 的默认空闲超时多为 60–90 秒,但部分政企网络设备甚至低至 30 秒。
- 若你设
heartbeat为 30 秒,而某台防火墙超时是 32 秒,那仍有 2 秒窗口期可能丢包导致断连 - 服务端收到 ping 后必须立即回 pong,且前端要监控 pong 延迟;超过 5 秒没收到,就该主动
ws.close(4999)触发重连,而不是等 onclose 被动响应 - 美股类 API(如 AllTick、Polygon)明确要求 ping 间隔 ≤10 秒,否则直接踢下线
推荐节奏:setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send('{"type":"ping"}'); }, 10000);
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
onclose 的 event.code 决定要不要重连
盲目重连反而加重服务端压力,还可能被限流。必须根据 event.code 区分场景:
-
event.code === 1000:服务端正常关闭,比如用户登出、会话过期,此时不应重连 -
event.code === 1006:底层 TCP 异常中断(无 close 帧),大概率是网络抖动或中间设备静默断开,应立即启动指数退避重连 -
event.code >= 4000:业务自定义错误码,比如4001表示 token 过期,需先刷新凭证再重连,否则徒劳
容易踩的坑:用 ws.onclose = () => connect() 这种无判断的写法,遇到 1000 关闭也会无限循环建连,最终被服务端拒绝。
服务端 KeepAliveInterval 和客户端心跳必须协同
ASP.NET Core 的 KeepAliveInterval、Gorilla WebSocket 的 SetPingHandler、Spring Boot 的 webflux.websocket.max-frame-payload-length,这些不是“可选项”,而是保活链路上的必要一环。
- 若服务端启用了 ping 自动下发(如 ASP.NET Core 设
KeepAliveInterval = TimeSpan.FromSeconds(30)),客户端却没配 pong handler,连接会在第一次服务端 ping 后因无响应被断开 - Node.js 的
ws库默认不自动处理 pong,必须显式调用ws.on('pong', () => {...})并重置心跳计时器 - PHP Swoole 中若未在
onMessage里对opcode === WEBSOCKET_OPCODE_PING做响应,同样会被判定为失联
关键点:服务端发 ping 是为了探测客户端是否存活,客户端回 pong 是为了告诉服务端“我还在”,二者缺一不可;单向心跳等于没做。










