websocket连接保活需客户端、服务端、nginx三层协同:nginx须配置proxy_read_timeout≥86400并透传upgrade头;服务端按框架设心跳参数(如socket.io pinginterval=10s);客户端主动发送ping消息、超时5秒未收pong即断连重试,且采用指数退避重连。

WebSocket连接不是“设个超时就能不掉线”,而是必须在客户端、服务端、中间件三层同时做保活动作,缺一不可。单独改某一层的 timeout 参数,大概率无效。
nginx反向代理层:proxy_read_timeout 必须显式延长
绝大多数“空闲60秒断连”问题,根子在 nginx。它默认 proxy_read_timeout 是 60 秒——只要后端 60 秒内没往客户端发数据,nginx 就主动 FIN 断开 TCP 连接,且不通知后端。
- 必须在
location块中显式配置:proxy_read_timeout 86400;(建议设为 24 小时,留足余量) - 必须透传 WebSocket 升级头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";,否则降级为 HTTP,心跳根本走不通 - 心跳间隔(如 30 秒)必须严格小于
proxy_read_timeout,推荐 ≤55 秒,避免边界抖动
服务端心跳配置:别只依赖框架默认值
Spring Boot 的 WebSocketHandler 本身不发心跳;Netty/WebFlux 的 WebServerSpec 默认 idle timeout 是 60 秒,但仅触发事件,不自动发 ping;Socket.IO 的 pingInterval 和 pingTimeout 才是真正在协议层起作用的心跳参数。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- Spring + Tomcat:无法通过
application.yml直接调大容器层超时,maxIdleTime硬编码为 60000ms,需 JVM 启动参数覆盖或换用 Netty 容器 - Socket.IO(Node.js):必须初始化时传入:
pingInterval: 10000, pingTimeout: 3000,否则默认 25s 心跳 + 5s 等待,极易被中间件掐断 - ASP.NET Core:
KeepAliveInterval控制的是服务端主动发 ping 帧的间隔,设为TimeSpan.FromSeconds(30)是常见安全值
JS客户端:不能只等 onclose,要主动探测+退避重连
浏览器 WebSocket API 不暴露 sendPing(),所以必须用业务消息模拟心跳;且 onclose 触发时连接已死,无法再发任何数据,重连逻辑必须提前启动。
- 连接建立后立即启动定时器:
setInterval(() => ws.send(JSON.stringify({ type: "ping" })), 30000) - 收到
pong响应才认为链路存活;超时未响应(比如 5 秒)就ws.close(4000, "heartbeat timeout")主动断开 - 重连不能无脑
setTimeout(connect, 1000):首次失败立即重试,第 2 次延迟 2 秒,第 3 次 4 秒,上限封顶到 30 秒(Math.min(1000 * 2 ** attempt, 30000)) - 切后台/休眠时,
visibilitychange事件要暂停心跳发送,恢复时重置计时器,避免堆积
容易被忽略的关键点:断开原因永远比超时值更重要
你看到的 event.code === 1006(abnormal closure)几乎不提供有效线索;真正该查的是服务端日志里 connection closed due to ping timeout 或 nginx access log 中对应连接的最后一条记录时间戳。如果心跳包能发出去但收不到 pong,问题一定出在服务端处理逻辑阻塞、消息队列积压,或客户端网络已切换但未触发 onclose——这种静默失联,只能靠双向心跳+超时裁决发现。










