90%的前端WebSocket问题出在连接管理、状态判断和重连逻辑:protocols参数影响服务端握手校验;readyState为1不保证send()成功,需检查WebSocket.OPEN并监控bufferedAmount;onmessage重复赋值会导致消息丢失,应使用addEventListener;重连须判别event.wasClean与code,并实施指数退避。
websocket 不是“用不用”的问题,而是“怎么用对、怎么不出错”的问题。直接上结论:90% 的前端 websocket 问题,都出在连接管理、状态判断和重连逻辑这三块,而不是协议原理本身。
WebSocket 构造函数的第二个参数 protocols 到底有什么用
这个参数不是摆设,它决定了服务端是否接受连接。比如你传 ['chat', 'notify'],服务端必须明确支持其中至少一个子协议,否则会直接拒绝握手(返回 HTTP 400 或静默断开)。
- 实际场景中,微服务网关或反向代理(如 Nginx)常根据
Sec-WebSocket-Protocol头做路由分发;不传或传错,请求可能被转发到错误后端 - 如果服务端没启用子协议校验,传了也无害;但一旦启用了,
new WebSocket(url, '')和new WebSocket(url)效果不同——后者不带Sec-WebSocket-Protocol头,会被拒绝 - 调试时可抓包看请求头里有没有
Sec-WebSocket-Protocol: chat,没有就说明参数没生效或被过滤了
readyState 为 1 就一定能 send() 吗
不能。虽然 readyState === 1 表示连接已打开,但网络抖动、代理超时、服务端写缓冲满等情况,都可能导致 send() 立即抛错或静默失败。
- 务必在
send()前加判断:if (socket.readyState === WebSocket.OPEN),别只写=== 1(语义更清晰,且避免 magic number) -
bufferedAmount是关键指标:如果它持续增长,说明数据发不出去,不是连接断了,而是卡在了发送队列里——这时候重连没用,得等或降频 - 某些浏览器(如旧版 Safari)在页面切后台时会暂停 WebSocket 发送,
bufferedAmount突增就是信号
onmessage 回调里反复赋值 onmessage 会导致消息丢失
这是高频翻车点。每次重新赋值 socket.onmessage = function() {...},都会覆盖前一个监听器,旧的回调永远不会再执行。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 正确做法是用
addEventListener('message', handler),它支持多次绑定;移除用removeEventListener('message', handler) - 如果用
onmessage,必须确保全局唯一赋值点(比如只在初始化时设一次),后续逻辑通过闭包或状态变量控制行为,而不是重写整个 handler - Vue/React 组件卸载时没清理
addEventListener,容易导致多个重复监听器累积,同一消息被处理多次
断线重连时最常忽略的两个细节
自动重连不是加个 setTimeout 就完事。真实环境里,重连失败往往不是因为代码没写,而是因为策略太简单。
立即学习“前端免费学习笔记(深入)”;
- 不要在
onclose里无条件重连:event.wasClean === false才代表异常断开;event.code === 1000是正常关闭,不该重连 - 指数退避必须做:第一次 1s,第二次 2s,第三次 4s……否则在服务端雪崩或 DNS 故障时,前端会发起海量无效连接请求,加重问题
- Nginx 默认
proxy_read_timeout 60,如果业务心跳间隔 >60s,连接会被代理层静默断开——这时onclose触发,但event.code可能是 1006(abnormal closure),不是服务端主动关的
真正难的从来不是“连上”,而是连得稳、发得准、断得明、重得对。这些点藏在文档角落,却决定着线上 WebSocket 是“实时通道”还是“定时失联报警器”。

















