聊天、行情推送、在线协作……这些功能都要求服务器主动推送数据,而传统 HTTP 是"一问一答"的。WebSocket 在单个 TCP 连接上实现了全双工通信,是实时应用的标配方案。本文讲清原理并给出可运行示例。
1. HTTP 轮询的痛点
在没有 WebSocket 的年代,前端只能定时发请求"问"服务器有没有新数据:
- 轮询:每隔几秒请求一次,大量请求空转,浪费带宽和服务器资源。
- 长轮询:请求挂起直到有数据才返回,减少了空转,但连接管理复杂、延迟仍偏高。
核心矛盾:HTTP 是单向请求-响应模型,服务器无法主动推送,WebSocket 正是为此而生。
2. 握手:从 HTTP 升级而来
WebSocket 连接以一次 HTTP 请求开始,通过 Upgrade 头协商升级协议:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务器验证 Sec-WebSocket-Key 后返回 101 Switching Protocols,连接升级成功。此后双方就可以在同一条 TCP 连接上双向收发消息,不再有 HTTP 头部的额外开销。
3. 数据帧格式
升级后传输的不再是 HTTP 报文,而是轻量的 WebSocket 帧,关键字段有:
FIN:是否为最后一帧(消息可分片传输)。opcode:帧类型,0x1 文本、0x2 二进制、0x8 关闭、0x9 ping。mask:客户端发往服务器的帧必须掩码。payload length:7 位起,大消息按扩展规则占更多字节。
帧头通常只有 2~14 字节,这是比 HTTP 轮询高效的重要原因。
4. 浏览器端示例
const ws = new WebSocket("wss://echo.websocket.org");
// 连接建立后发送消息
ws.onopen = () => {
ws.send("你好,服务器!");
};
// 收到服务器消息
ws.onmessage = (event) => {
console.log("收到:", event.data);
};
// 连接关闭(可在此重连)
ws.onclose = () => {
console.log("连接已关闭");
};
// 心跳保活,防空闲连接被断开
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send("ping");
}
}, 30000);
5. 用 wscat 测试服务器
# 安装 wscat(Node.js 环境)
npm install -g wscat
# 连接公共回声服务器
wscat -c wss://echo.websocket.org
# 连接本地开发服务器
wscat -c ws://localhost:3000
写一个最简单的 Node.js 服务器做实验:
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 3000 });
wss.on("connection", (socket) => {
socket.on("message", (data) => {
// 收到后原样回给客户端
socket.send(`echo: ${data}`);
});
});
6. 生产环境的注意点
- 心跳保活:中间代理会回收空闲连接,应用层要定期发 ping/pong。
- 断线重连:移动网络切换会导致连接断开,客户端应实现指数退避重连。
- 负载均衡:长连接需要"粘性会话"(sticky session),或改用消息总线广播。
- 鉴权:不要在 URL 里放敏感 token,优先在握手后的第一条消息里校验。
💡 学习建议:先用公共 echo 服务器跑通浏览器端,再自己写 Node 服务器,最后给聊天室加上心跳和重连。三步走完,WebSocket 的坑你基本都踩过了。