WebSocket 与 HTTP 的区别
HTTP 是请求响应的短连接模型:浏览器发请求、服务器回响应,一次交互就结束,服务器没法主动把消息推给浏览器。聊天、行情、协同编辑这类实时场景,如果一直靠轮询,要么延迟高,要么浪费大量请求。WebSocket 提供一条浏览器与服务器之间的长连接,双向、低延迟,是实时通信的标准方案。
- 长连接建立后一直保持,双向收发。
- 握手从 HTTP 升级协议建立连接。
- 心跳用 ping 与 pong 保活连接。
- 集群广播多节点下的消息分发。
握手:从 HTTP 升级到 WebSocket
WebSocket 不是凭空建立的,它先走一次 HTTP 请求,带上 Upgrade 头,服务器同意后协议切换成 WebSocket。浏览器端用 new WebSocket 一行代码就能发起,服务端需要校验握手请求里的 Sec-WebSocket-Key 并返回正确的响应头。
const ws = new WebSocket("wss://example.com/chat");
ws.onopen = function(){ ws.send("连接已建立"); };
ws.onmessage = function(e){ console.log(e.data); };
服务端框架通常已经封装了握手细节,你只需要定义连接、消息、关闭三个核心事件。握手成功之后,这条 TCP 连接就一直复用,不再每次请求都重新建连。
消息帧与数据格式
WebSocket 通信的最小单位是帧,文本帧、二进制帧、关闭帧各有用途。业务上一般约定一种消息协议:用 JSON 包一层 type 和 data,客户端根据 type 分发处理,服务器广播时也按 type 区分是聊天消息、在线状态还是系统通知。
{"type":"chat","data":{"from":"u1","text":"你好"}}
大消息可以分片发送,压缩可以用扩展协商。实践上保持消息体精简、字段稳定,后续加字段要兼容旧客户端,避免一把梭改格式导致线上抖动。
心跳保活与断线重连
长连接挂在中间代理上,一段时间没有数据就会被认为是死连接被清理,客户端还以为连着。常规做法是定时发 ping 帧,服务器回 pong;连续几次没收到 pong 就判定连接断开,主动关闭并让客户端重连。
const heartbeat = setInterval(function(){
if (ws.readyState === WebSocket.OPEN) ws.send("ping");
}, 30000);
ws.onclose = function(){ setTimeout(reconnect, 3000); };
重连要带退避:失败后等待时间逐步拉长,避免断网瞬间所有客户端同时重连把服务器打爆。重连成功后还要做状态补偿,把断线期间漏掉的消息补回来。
服务端广播与多端同步
单机场景广播很简单:维护一个连接集合,遍历调用 send。但生产环境要区分房间、会话,还要考虑一个用户多端登录,同一消息要推给他的所有连接。常见的做法是在服务端维护连接上下文,把 userId 与一组连接对应起来,广播时按房间成员查表分发。
// 伪代码:房间广播
const room = rooms.get(roomId);
for (const conn of room.connections) {
conn.send(JSON.stringify(msg));
}
消息量大时还可以做合并推送:把短时间内的多条消息攒在一起批量下发,减少小包数量,提升吞吐。
集群部署下的连接管理
一台机器撑不住海量连接,WebSocket 服务必然要横向扩容。问题是:用户 A 连接在节点 1,用户 B 在节点 2,广播消息怎么跨节点送达?主流方案是用 Redis Pub/Sub 或消息队列做节点间总线:每个节点订阅频道,广播时把消息发到总线,所有节点收到后推给各自管理的连接。
- Redis Pub/Sub实现简单,适合中小规模。
- 消息队列有持久化与重试,适合大流量。
连接在节点间的分布不透明,用户重连可能落在不同节点,所以登录态要放在共享存储里,连接本地只存连接句柄。
安全与限制
WebSocket 同样要防越权:握手时校验身份令牌,之后每条消息都要按会话鉴权,不能只信连接建立时的一次认证。生产环境必须用 wss 加密传输,防止中间人窃听。还要设置连接数上限、消息大小上限与频率限制,防止个别用户刷消息拖垮服务。
// 握手鉴权示例(伪代码)
if (!validToken(req.headers["authorization"])) {
return 401;
}
跨域问题用 Origin 校验,非白名单来源直接拒绝握手。关闭连接时做优雅退出,把连接从集合里移除,避免僵尸连接占着内存不释放。
落地清单
- 需要服务器主动推送时用 WebSocket,别用轮询硬撑。
- 定义 type 与 data 的消息协议,保持兼容演进。
- 加心跳保活,检测假死连接并及时回收。
- 重连带退避,重连成功后做消息补偿。
- 集群用 Redis Pub/Sub 或消息队列做跨节点广播。
- wss 加密、握手鉴权、限制消息大小与频率。