WebSocket 服务端
问题
如何在服务端实现 WebSocket?连接管理、心跳检测、房间机制和集群广播是怎么做的?
答案
WebSocket 服务端架构
基础实现(ws 库)
ws-server.ts
import { WebSocket, WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
// 连接管理
const clients = new Map<string, WebSocket>();
wss.on('connection', (ws, req) => {
const userId = parseUserId(req);
clients.set(userId, ws);
// 心跳检测
let isAlive = true;
ws.on('pong', () => { isAlive = true; });
const heartbeat = setInterval(() => {
if (!isAlive) {
clients.delete(userId);
return ws.terminate();
}
isAlive = false;
ws.ping();
}, 30000);
// 消息处理
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
handleMessage(userId, message);
});
ws.on('close', () => {
clearInterval(heartbeat);
clients.delete(userId);
});
});
// 发送消息给指定用户
function sendToUser(userId: string, data: unknown) {
const ws = clients.get(userId);
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
// 广播给所有在线用户
function broadcast(data: unknown) {
const message = JSON.stringify(data);
clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
});
}
集群广播(Redis Pub/Sub)
cluster-broadcast.ts
import Redis from 'ioredis';
const pub = new Redis();
const sub = new Redis();
// 当本机收到消息需要广播时
function clusterBroadcast(channel: string, data: unknown) {
pub.publish(channel, JSON.stringify(data));
}
// 每个实例订阅频道
sub.subscribe('chat:messages');
sub.on('message', (channel, message) => {
// 收到消息后广播给本机连接的客户端
const data = JSON.parse(message);
broadcast(data);
});
Socket.IO(生产推荐)
socketio-server.ts
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
const io = new Server(server, {
cors: { origin: '*' },
});
// Redis 适配器(支持集群)
io.adapter(createAdapter(pubClient, subClient));
io.on('connection', (socket) => {
// 加入房间
socket.join(`user:${socket.data.userId}`);
// 房间消息
socket.on('join-room', (roomId) => {
socket.join(roomId);
});
socket.on('message', (data) => {
// 向房间内广播
io.to(data.roomId).emit('message', data);
});
});
// 向特定用户发消息
io.to(`user:${userId}`).emit('notification', data);
常见面试问题
Q1: 如何处理 WebSocket 断线重连?
答案:
服务端配合:
- 为每个连接分配唯一 ID
- 存储未送达消息(离线队列)
- 重连后根据 ID 恢复会话,推送离线消息
Q2: 多台服务器部署时,WebSocket 消息如何同步?
答案:
使用 Redis Pub/Sub 做跨实例广播。Socket.IO 有现成的 @socket.io/redis-adapter。
Q3: WebSocket 和 HTTP 长轮询怎么选?
答案:
- WebSocket:双向实时通信、高频消息(聊天、协同编辑)
- 长轮询:兼容性好、低频消息、简单场景
- SSE:服务端单向推送(通知、AI 流式输出)
Q4: 心跳检测的作用?
答案:
- 检测连接是否存活(客户端断网不会触发 close 事件)
- 防止中间设备(NAT、代理)关闭空闲连接
- 通常 30s 一次 ping/pong
相关链接
- WebSocket 与 SSE - 前端 WebSocket
- 设计实时通讯系统 - IM 系统设计
- 设计直播弹幕系统 - 弹幕系统