1006是客户端在tcp连接异常中断且未收到close帧时自动生成的状态码,非服务端发送;常见根因包括代理未透传upgrade头、nginx超时配置过短、心跳契约断裂、移动端系统回收及nat超时等。

WebSocket自动断开不是“随机故障”,而是协议层、网络层或配置层某处契约被打破的明确信号。最常见且高频的异常关闭状态码是 1006,它不指向具体错误,只说明连接在未发送 CLOSE 帧的情况下突然终止——这背后一定有可定位的根因。
为什么 WebSocket 连接建立后几秒就关闭(1006)
这不是心跳问题,而是握手失败。浏览器发出 Upgrade 请求后,若服务端没返回 HTTP/1.1 101 Switching Protocols,或响应头缺 Upgrade: websocket、Connection: upgrade,连接会立刻中止。
- 用 Chrome DevTools 的 Network 面板过滤 WS 请求,点开看 Response Headers 是否含
101和两个关键 header - 检查 Nginx 等反向代理是否透传了
Upgrade和Connection头:必须显式配置proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade" - curl 模拟握手时注意
Sec-WebSocket-Key是 base64 随机值,不能硬写;服务端需返回匹配的Sec-WebSocket-Accept
为什么连接能建上但 30–90 秒后必断(1006 或 1001)
这是典型的心跳契约断裂。WebSocket 本身不保活,依赖 ping/pong 帧维持中间设备(Nginx/ALB/防火墙)的 TCP 连接不被回收。任一端未按约定频率发帧,超时后设备直接 RST。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 客户端需主动发
ping(可用ws.send('{"type":"ping"}'),原生ws.ping()在多数浏览器不可用) - 服务端必须响应
pong,且响应延迟要小于中间件的空闲超时(如 Nginx 默认proxy_read_timeout 60s,建议设为 ≥90s) - Spring Boot 2.6+ 中
WebSocketSession#setHeartbeat()已废弃,改用afterConnectionEstablished()手动调度定时任务
为什么移动端切后台或 Wi-Fi 切 4G 就断(1006)
系统级资源管控导致。iOS Safari 和 Android WebView 在页面不可见或网络切换瞬间会冻结或终止 WebSocket 连接,且不触发标准事件。
- 监听
document.visibilityState:页面切回可见时,不要复用旧连接,直接new WebSocket()新建 - 监听
window.online/window.offline:离线时不重连,上线后立即重建(避免盲目轮询) - 避免在
onclose中直接setTimeout(reconnect, 0),应加判断:if (event.code === 1006 && !navigator.onLine) return
Nginx 配置错一个参数就断连
哪怕 WebSocket 握手成功、心跳也正常,Nginx 的几个 timeout 参数仍可能单方面 kill 连接。它们和心跳间隔不是同一维度,必须独立调大。
-
proxy_read_timeout:Nginx 等待后端响应的最大时间,必须 > 心跳间隔 + 网络抖动余量(例如心跳 30s,这里至少设 90s) -
proxy_send_timeout:Nginx 向后端发数据的超时,同样需 ≥90s -
proxy_buffering off必须关闭,否则 Nginx 可能缓存 pong 帧,导致客户端收不到响应
真正难排查的从来不是“有没有心跳”,而是心跳帧是否被完整送达、中间设备是否认可该心跳语义、以及系统是否在你不知情时静默终结了 socket。每次 1006 都值得抓包验证 ping/pong 是否成对出现——这是唯一绕过所有抽象层直击真相的方式。










