在需要实时通信的场景里,轮询 HTTP 接口既浪费带宽又存在延迟。WebSocket 协议通过一条长连接实现浏览器与服务器的全双工、双向通信,是聊天室、在线协作、行情推送的基石。本文从原生 WebSocket API 讲起,再落地到 Socket.IO,并覆盖心跳、Nginx 代理、SSE 替代方案与安全鉴权,让你拿到即可用。
一、HTTP 轮询的痛点:为什么需要 WebSocket
传统的「短轮询」让客户端每隔几秒请求一次接口,哪怕没有新消息也要空跑;「长轮询」虽能减少空请求,但每次响应后都要重建连接。两者都逃不开「请求-响应」的单向模型,服务器无法主动推送。WebSocket 在握手后把 HTTP 连接升级为持久化的双向通道,数据帧开销仅 2–14 字节,延迟从秒级降到毫秒级。
| 通信方式 | 方向 | 连接模型 | 典型延迟 | 适用场景 |
|---|---|---|---|---|
| 短轮询 | 单向 | 反复新建 | 秒级 | 不频繁的状态查询 |
| 长轮询 | 单向 | 挂起后重建 | 亚秒级 | 简单通知 |
| SSE | 服务器→客户端 | 单条持久 | 毫秒级 | 日志流、行情推送 |
| WebSocket | 双向 | 单条持久 | 毫秒级 | 聊天、协作、游戏 |
二、原生 WebSocket API:五分钟上手
2.1 浏览器客户端
浏览器内置 WebSocket 对象,握手成功后即可双向收发。消息建议统一用 JSON 序列化,方便扩展字段。
const ws = new WebSocket("wss://example.com/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ type: "join", room: "news" }));
};
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
console.log("收到:", msg);
};
ws.onclose = () => console.warn("连接已关闭,准备重连");
ws.onerror = (e) => console.error("WebSocket 错误", e);
2.2 Node.js 服务端(ws 库)
服务端用轻量的 ws 库即可,比内置实现更省心。下面是一个最小回声 + 广播服务器。
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws, req) => {
console.log("新连接来自", req.socket.remoteAddress);
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
// 广播给所有客户端
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify({ echo: msg }));
}
});
});
ws.on("close", () => console.log("连接断开"));
});
console.log("WebSocket 服务已启动 :8080");
三、心跳与重连:别让连接悄悄死掉
中间代理(如 Nginx、负载均衡)常会因空闲超时悄悄断开连接,而 TCP 层并不总能感知。标准做法是服务端定时发 ping 帧,客户端回 pong,超时未响应则主动关闭。如果线上出现频繁断连,可参考本站《WebSocket 频繁断连?Nginx 超时与心跳排查实战》定位。
// 服务端心跳:30s 一次,超时即清理
wss.on("connection", (ws) => {
ws.isAlive = true;
ws.on("pong", () => { ws.isAlive = true; });
});
const timer = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on("close", () => clearInterval(timer));
四、Socket.IO 实战:房间、广播与自动重连
当业务需要「房间」「命名空间」「断线自动重连」「传输降级」时,Socket.IO 比原生 API 省力得多。它内置心跳、重连和轮询降级,开箱即用。
// 服务端:房间与广播
const io = require("socket.io")(3000);
io.on("connection", (socket) => {
socket.on("join", (room) => {
socket.join(room);
io.to(room).emit("system", `${socket.id} 进入了 ${room}`);
});
socket.on("chat", ({ room, text }) => {
io.to(room).emit("chat", { from: socket.id, text });
});
});
// 客户端:自动重连由库内置
const socket = io("https://example.com", { path: "/socket.io" });
socket.emit("join", "news");
socket.on("chat", (m) => renderMessage(m));
| 维度 | 原生 WebSocket | Socket.IO |
|---|---|---|
| 断线重连 | 需自己实现 | 内置,自动重试 |
| 房间/广播 | 自行维护集合 | 原生 rooms 支持 |
| 传输降级 | 无 | 不支持时降级长轮询 |
| 体积/心智负担 | 极轻,零依赖 | 较重,需客户端库 |
| 适用 | 简单双向、极致轻量 | 复杂实时业务 |
五、生产部署:Nginx 反向代理与超时
WebSocket 握手依赖 Upgrade 和 Connection 头,Nginx 默认不转发。必须在反代配置中显式放行,并放宽读超时,否则长连接会被提前掐断。关于 Nginx 反代的基础姿势,可看《Nginx 反向代理实战》。
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 长连接别被默认 60s 掐断
proxy_send_timeout 3600s;
}
六、SSE:单向实时推送的轻量替代
如果只需服务器→客户端的推送(如日志流、通知),SSE(Server-Sent Events) 用普通 HTTP 即可,无需升级协议,浏览器用 EventSource 原生支持,还能自动重连。
// 服务端(Node)
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
setInterval(() => res.write(`data: ${JSON.stringify({ t: Date.now() })}
`), 1000);
// 客户端
const es = new EventSource("/stream");
es.onmessage = (e) => console.log("推送:", JSON.parse(e.data));
七、鉴权与安全
WebSocket 握手是一次 HTTP 请求,可在 URL 查询参数或 Sec-WebSocket-Protocol 子协议里带令牌,服务端在 connection 事件里校验;务必使用 wss://(TLS)并校验 Origin 防跨站滥用。鉴权体系的更多细节参见《前端鉴权实战:Session、JWT 与 OAuth2》。
wss.on("connection", (ws, req) => {
const url = new URL(req.url, "http://x");
const token = url.searchParams.get("token");
if (!verifyToken(token)) return ws.close(4001, "unauthorized");
});
八、选型速查与落地清单
| 场景 | 推荐方案 |
|---|---|
| 单向推送(行情/日志) | SSE |
| 简单双向、极低延迟 | 原生 WebSocket |
| 聊天/协作/多房间 | Socket.IO |
| 跨语言异构客户端 | 原生 WebSocket(通用) |
落地前检查清单:① 全程 wss:// 加密;② 服务端心跳 + 客户端重连;③ Nginx 放行 Upgrade 并放宽超时;④ 鉴权令牌校验与 Origin 白名单;⑤ 消息体做大小与频率限制防滥用。掌握这些,你就能把任意「实时通信」需求稳稳落地。




