WebSocket连接频繁断开绝大多数不是网络波动,而是某层主动切断:code 1006为TCP层异常(如Nginx发RST、NAT超时),1001或“going away”表明服务端主动关闭,4001多因鉴权失败;需结合onclose.code、抓包确认FIN/RST来源,并严格检查Nginx三项配置——proxy_http_version 1.1、Upgrade头透传、Connection头设为"upgrade",且proxy_read_timeout须≥心跳间隔×2。

WebSocket连接频繁自动断开,**绝大多数情况不是网络波动导致的,而是某一层(Nginx、服务端、客户端运行时)主动切断了连接**。真正要做的,是看 onclose.code、抓包确认 FIN/RST 来源、检查代理配置是否“假装透传”。
怎么看 onclose.code 判断谁切断了连接
别只监听 onclose,必须读取它的 code 和 reason 字段——这是最直接的“断连口供”:
-
code === 1006:TCP 层异常中断,不是你的代码问题,大概率是 Nginx 发 RST、NAT 超时、防火墙静默丢包;此时reason通常为空或不可靠 -
code === 1001或reason含"going away"、"shutdown":服务端主动关闭,立刻查服务端日志,关键词如heartbeat timeout、read timeout、client idle -
code === 4001或reason含"auth failed"、"duplicate":协议层校验失败,常见于 token 过期、重复登录踢人逻辑触发,和连接稳定性无关,属于业务逻辑误判 - 控制台没触发
onerror,但高频触发onclose:基本可锁定是中间件(Nginx/ALB)在空闲后强制断连,而非你的 JS 抛错
Nginx 配置漏一项,WebSocket 就当 HTTP 处理
Nginx 默认不认 WebSocket,所谓“配了 upgrade”不等于真透传——少一个分号、$http_upgrade 写成 $upgrade、Connection 值多一个空格,都会让它把连接当普通 HTTP 处理,60 秒无数据就发 FIN。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 必须同时满足三项:
proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade" -
proxy_read_timeout必须 ≥ 心跳间隔 ×2 + 网络抖动缓冲(例如心跳 30s,这里至少设90,生产环境建议86400) - 别信“我 reload 过配置”,用
nginx -t检查语法,再用curl -i -H "Connection: Upgrade" -H "Upgrade: websocket" http://your-nginx/ws手动验证握手响应头是否含101 Switching Protocols
心跳机制不能只发 ping,还要防客户端被系统冻结
移动端尤其 iOS Safari / WKWebView,切后台后系统会冻结 JS 定时器,setInterval 停摆,心跳彻底失效,NAT 超时后连接静默断开。
- 客户端心跳必须结合
document.visibilityState:页面隐藏时暂停setInterval,可见时立即检查连接状态并重发心跳 - 服务端不能只等客户端 ping —— 要主动发
websocket.PingMessage(Go)、ws.ping()(Node),并设置SetPongHandler或等效逻辑来重置读超时 - 别用 JSON 字符串模拟 ping:
ws.send('{"type":"ping"}')是应用层消息,无法触发 TCP 层保活;必须用原生ws.ping()或服务端发送标准 Ping 帧
抓包确认 FIN/RST 是谁发的,比猜快十倍
Wireshark 或 tshark 抓客户端和服务端之间流量,过滤 tcp.port == your_ws_port,看最后几个包:
- FIN/RST 源 IP 是你服务器公网 IP → 查服务端内核参数(
net.ipv4.tcp_keepalive_time)、Worker 进程是否崩溃、日志是否有 OOM 或 panic - FIN/RST 源 IP 是 Nginx 机器 → 立即回看 Nginx 配置,重点盯
proxy_read_timeout和Upgrade头是否透传成功 - 根本抓不到 FIN,只有大量 TCP 重传 → 典型 NAT 超时(家庭路由器、4G/5G 网关)或 iOS 切后台,这时只能靠更激进的心跳(如 15s)+ 客户端 visibility 监听补救
真正难的不是写重连逻辑,而是区分“连接被谁杀”和“为什么被杀”——onclose.code 是起点,抓包是铁证,Nginx 配置是最高频的漏点。所有“我加了心跳还是断”的案例,八成卡在 Nginx 少配了一行,或客户端心跳被页面休眠吃掉了。

















