WebSocket是“长连接”因其通过HTTP升级握手后复用TCP连接实现全双工通信,避免HTTP频繁建连开销,但需主动处理断线重连、心跳保活及readyState校验等边界问题。

WebSocket 长连接不是“一直不掉线”的魔法,而是靠一次 HTTP 升级握手 + 后续 TCP 连接复用实现的。它本身不保证永不中断,但设计目标是**维持单次建立的双向通道尽可能久**,避免轮询开销。
WebSocket 连接为什么算“长连接”
HTTP 默认每次请求都新建 TCP 连接(或复用有限时间的 keep-alive),而 WebSocket 在初始阶段走 HTTP,但一旦服务器返回 101 Switching Protocols,底层 TCP 连接就不再按 HTTP 规则关闭,而是被协议栈接管,持续用于二进制帧通信。
关键点:
- 不是靠心跳“伪装在线”,而是协议层定义了连接生命周期:从
CONNECTING→OPEN→CLOSING→CLOSED - 浏览器或 Node.js 的
ws库不会自动重连;readyState === WebSocket.OPEN才能发消息,否则会静默丢弃 - 真实网络中,NAT 超时、代理中断、休眠唤醒等都会导致连接意外断开,必须自己处理
onclose和重连逻辑
网页端发送消息前必须检查 readyState
很多人写 socket.send(...) 时不判断状态,结果消息发不出去也没报错——因为 send() 在非 OPEN 状态下直接静默失败。
正确做法:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 发消息前加判断:
if (socket.readyState === WebSocket.OPEN) { socket.send(data); } - 如果处于
CONNECTING,可缓存消息,监听onopen后再发 - 不要依赖
onerror捕获发送失败:它只触发底层传输错误,不覆盖业务层“连接未就绪”场景
Node.js 服务端用 ws 库时,ws.readyState 和 wss.clients 不实时同步
Node.js 的 ws 库里,wss.clients 是一个 Set,但它包含所有曾经连接过的 socket 对象,哪怕已断开但尚未被 GC 清理。直接遍历它并调用 ws.send() 很可能抛出 WebSocket is not open 错误。
安全写法:
- 永远检查
ws.readyState === WebSocket.OPEN再发消息 - 配合心跳机制:客户端每 30s 发
ping,服务端ws.on('pong', () => ws.isAlive = true),定时清理!ws.isAlive的连接 -
wss.clients只用于枚举,不用于状态断言
聊天消息广播时,忽略自己发的那条很常见但容易漏
比如用户 A 发一条“hello”,服务端收到后默认广播给所有人,A 自己的页面也会收到一份——造成重复渲染或本地 echo 错乱。
解决方式取决于你的路由逻辑:
- 如果是群聊广播,用
broadcast(data, excludeUser = userId)显式剔除发送者 - 如果是服务端统一推送,客户端需加判断:
if (message.from !== currentUserId) renderMessage(message) - 别依赖前端“不显示自己发的”来掩盖问题:服务端多推一次,就多占一次带宽和 CPU
new WebSocket(),而是处理连接闪断、消息堆积、重复渲染、跨域 cookie 丢失这些边界情况。WebSocket 协议本身很轻,但让它在各种弱网、多标签、后台页、PWA 场景下稳定工作,需要的不是“教程步骤”,而是对每个 readyState 切换、每条 onmessage 入口、每次 ws.send() 调用都保持警惕。

















