何时用 WebSocket
需要服务器主动推送的场景:
- 实时聊天 / 消息
- 实时协作(多人编辑)
- 实时数据(股票 / 监控)
- 游戏 / 在线状态
- 通知系统
HTTP 长轮询 / SSE(Server-Sent Events)是替代方案;WebSocket 双向 + 低延迟。
两个库
| ws | Socket.IO | |
|---|---|---|
| 协议 | 标准 WebSocket | 自己协议(基于 WS + 长轮询 fallback) |
| 体积 | 小 | 中 |
| 房间 / 命名空间 | 自己实现 | 内置 |
| 自动重连 | 自己写 | 内置 |
| 浏览器支持 | 现代 | 含 fallback |
| 推荐 | 想直接控制 WebSocket | 不想造轮子 |
ws:轻量原生
npm install ws
// server.js
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', ws => {
console.log('Client connected');
ws.on('message', data => {
console.log('Got:', data.toString());
// 广播给所有连接
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(`echo: ${data}`);
}
});
});
ws.on('close', () => console.log('Client gone'));
ws.on('error', err => console.error(err));
ws.send('Welcome');
});
客户端(浏览器):
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => ws.send('hello');
ws.onmessage = e => console.log('received:', e.data);
ws.onclose = () => console.log('disconnected');
ws.onerror = e => console.error(e);
心跳(保活)
WS 连接可能被 NAT / 代理在静默时关闭。加心跳:
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', ws => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
const interval = setInterval(() => {
wss.clients.forEach(ws => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => clearInterval(interval));
集成到 Express
import express from 'express';
import { WebSocketServer } from 'ws';
import { createServer } from 'http';
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
wss.on('connection', ws => {
ws.send('Welcome');
});
app.get('/', (req, res) => res.send('HTTP works too'));
server.listen(3000);
同一端口 HTTP + WebSocket 共存。
Socket.IO:开箱即用
npm install socket.io
import { Server } from 'socket.io';
import { createServer } from 'http';
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: '*' },
});
io.on('connection', socket => {
console.log('Client connected:', socket.id);
socket.on('chat:message', msg => {
io.emit('chat:message', { from: socket.id, msg }); // 广播
});
socket.on('join', room => {
socket.join(room);
io.to(room).emit('user:joined', socket.id);
});
});
httpServer.listen(3000);
客户端:
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');
socket.emit('chat:message', 'hello');
socket.on('chat:message', data => console.log(data));
房间 / 命名空间(Socket.IO 强项)
// 命名空间(隔离不同业务)
const chat = io.of('/chat');
chat.on('connection', socket => { ... });
// 房间(按需聚合)
socket.join('room-42');
socket.to('room-42').emit('msg', 'hi room');
io.in('room-42').emit('msg', 'hi room');
横向扩展(多实例)
单机 WS 几万连接没问题——更多就要多机:
- Socket.IO:用
@socket.io/redis-adapter经 Redis 同步事件 - ws:自己用 Redis Pub/Sub 同步
性能 / 内存
- 每个连接占 KB 级内存(OS socket + Node 状态)
- 单机能扛 10K-100K 连接(取决于消息频率)
- 大规模考虑 sticky session(负载均衡器把同一用户路由到同一实例)
安全
- TLS(wss://):跨公网必须
- 鉴权:连接时验 JWT / cookie,未授权 close
- 限流:单 IP 最多几个连接 + 消息频率限制
- 消息大小:限制 max payload size(默认 ws 100 MB,太大)
const wss = new WebSocketServer({
port: 8080,
maxPayload: 64 * 1024, // 64 KB
});
坑
- WS 没有自动重连(ws 库)—— 客户端必须自己写
- 心跳是必须的——否则连接可能在你不知道时已经死了
- Socket.IO 客户端和服务器版本必须一致——不然协议不兼容
- 大量广播 = 大量 CPU——考虑用 Redis Pub/Sub 解耦
下一篇:PostgreSQL 客户端。