websocket 握手成功需满足三要素:响应状态码为 http/1.1 101 switching protocols、包含 upgrade: websocket 和 connection: upgrade 头、返回非空 sec-websocket-accept 值;缺一则握手失败,导致连接立即关闭且 code 为 1006。

不是心跳没配,而是连接根本没真正建立成功——1006、立即关闭、无错误日志,90% 都卡在握手阶段或代理透传环节。
怎么看 WebSocket 握手是否成功
浏览器 Network 面板里点开 WS 请求,直接看 Response Headers:
- 必须有
HTTP/1.1 101 Switching Protocols—— 不是 200、403、502 - 必须含
Upgrade: websocket和Connection: Upgrade - 必须有
Sec-WebSocket-Accept值(一串 base64,不能为空)
缺任意一项,说明服务端或中间层(Nginx / CDN / LB)没完成协议升级。此时连接已“假建立”,浏览器立刻关掉,你看到的“断开”其实是握手失败的副作用。
Nginx 代理漏配 upgrade 头导致静默断连
哪怕 URL 正确、服务端逻辑没问题,Nginx 默认会把 WebSocket 当普通 HTTP 处理,60 秒无数据就发 RST 强制断开,客户端收不到任何帧,onclose.code 固定为 1006,event.wasClean 为 false。
必须在 location 块里显式配置:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300; proxy_send_timeout 300;
注意:$http_upgrade 不能写成 $upgrade,也不能漏引号或分号;proxy_read_timeout 必须 ≥ 心跳间隔 × 2(比如心跳 30s,这里至少设 90)。
后端鉴权头未透传或未响应 subprotocol
前端带 token 连接时写法:new WebSocket(wsUri, [TokenKey]),这会发 Sec-WebSocket-Protocol: TokenKey 头;若后端没在响应里回相同的 Sec-WebSocket-Protocol,部分浏览器(尤其 Safari)会直接断连。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
Spring Boot + WebSocket 场景下常见坑:
- SecurityConfiguration 没放行
/ws/**路径,导致鉴权拦截返回 403 - @ServerEndpoint 注解漏了
subprotocols = {"token"},或没在onOpen里校验session.getRequestParameterMap().get("token") - 用了 Spring Security 但没配置
webSocketAllowedOrigins,Origin 校验失败
临时验证:用 curl 模拟握手,观察是否返回 101 + 合法 Sec-WebSocket-Accept:
curl -i -N \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ -H "Sec-WebSocket-Protocol: mytoken" \ https://your-api.com/ws
客户端事件绑定时机错位
WebSocket 实例创建后,onopen/onerror 必须**立即绑定**,不能等异步操作(如 token 获取完成)后再赋值。否则在连接快速建立又失败的瞬间,事件监听器还没挂上,onerror 就永远不会触发,只留下一个 readyState === 0 的悬空对象。
错误写法:
const ws = new WebSocket(url);
await getToken(); // 网络请求延迟
ws.onopen = () => { ... }; // 此时连接可能已失败并关闭
正确写法:
const ws = new WebSocket(url);
ws.onopen = () => { console.log('connected') };
ws.onerror = (e) => { console.error('handshake failed', e) };
ws.onclose = (e) => { console.log('closed', e.code) };
Vue / React 中还要注意组件卸载时手动 ws.close(),否则残留连接可能干扰重连状态判断。
真正难排查的不是“连不上”,而是“连上了又马上消失”——它往往发生在你没看到的地方:Nginx 配置少一个分号、后端响应头漏一个字段、前端多等了一次 await。别急着重连逻辑,先确认那条 101 响应是不是真的到了浏览器手里。










