1005错误本质是“未收到关闭码”,即对端静默断连未发Close帧,故event.reason为空;常见于代理超时、服务崩溃、网络瞬断或网关丢帧,需结合日志与抓包定位根因。

1005 错误本质是“没收到任何关闭码”,不是客户端或服务端主动报错,而是连接被静默切断——这意味着你看到的错误只是结果,不是原因。
为什么 onclose 里 event.code === 1005 但 event.reason 为空?
这是 RFC-6455 明确规定的:1005(SRStatusCodeNoStatusReceived)表示对端在断开连接时**根本没发 Close 帧**。常见于:
- 中间代理(Nginx、ELB、HAProxy)超时后直接
FIN断连,不走 WebSocket 协议流程 - 服务端进程崩溃、OOM 被杀、容器重启,来不及发送 Close 帧
- 客户端所在设备休眠、Wi-Fi 切换、移动网络瞬断,TCP 连接已失联但 JS 线程尚未感知
- Spring Cloud Gateway 等网关层无法透传或监听底层 close 事件,转发时丢弃了状态码
此时 event.code 是浏览器补的占位值,event.reason 必然为空——别指望它告诉你“为什么”。
如何区分 1005 是网络抖动还是服务端崩了?
单靠客户端日志无法判断,必须结合服务端和中间件日志交叉验证:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 查 Nginx 访问日志:如果
upstream_status字段缺失或为-,大概率是proxy_read_timeout触发的静默断连 - 查服务端连接数监控:若断连高峰伴随
ESTABLISHED连接数骤降,可能是服务端 GC 或 OOM - 抓包确认:用
tcpdump抓到FIN包但无0x88(Close 帧),基本锁定为代理或内核层切断 - 客户端加标记:在
onopen时记录performance.now()时间戳,断连时对比是否接近代理超时值(如 60s)
重连逻辑必须绕过 1005 的“假死”陷阱
很多代码把 1005 和 1006 一样处理,立刻重试——这在服务端真崩了时反而加重雪崩。正确做法是:
- 对
1005启动「延迟探测」:先等 2–3 秒,尝试发一条轻量 ping 消息;若无响应再走重连 - 重连前检查
navigator.onLine和fetch('/health')(HTTP 健康端点),排除本地网络问题 - 避免与
1011(服务器内部错误)混用指数退避:1011 应立即重试,1005 需先验活 - 服务端配合:在心跳 pong 帧里附带简单服务状态(如
{"status":"ok","ts":1716947580}),客户端可据此跳过无效重连
Spring Cloud Gateway 场景下 1005 的硬解法
如果你用的是 Spring Cloud Gateway,1005 很可能来自网关无法透传 close 状态。官方默认行为就是丢帧。必须手动拦截:
- 写一个
GlobalFilter,匹配Sec-WebSocket-Key头,将请求改写为直连后端地址(绕过 Gateway WebSocket 转发) - 或启用
spring.cloud.gateway.websocket.protected-headers=Sec-WebSocket-Protocol并升级到3.1.10+版本 - 更稳妥的做法:让前端直连真实 WebSocket 服务(如 Nginx 反向代理到后端),Gateway 只负责 HTTP API 流量
最后提醒一句:所有重连逻辑里,socket.close() 前务必清空 onmessage、onerror 引用,否则旧 socket 的回调可能在新连接上触发,造成状态混乱。










