跳到主要内容

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 断线重连?

答案

服务端配合:

  1. 为每个连接分配唯一 ID
  2. 存储未送达消息(离线队列)
  3. 重连后根据 ID 恢复会话,推送离线消息

Q2: 多台服务器部署时,WebSocket 消息如何同步?

答案

使用 Redis Pub/Sub 做跨实例广播。Socket.IO 有现成的 @socket.io/redis-adapter

Q3: WebSocket 和 HTTP 长轮询怎么选?

答案

  • WebSocket:双向实时通信、高频消息(聊天、协同编辑)
  • 长轮询:兼容性好、低频消息、简单场景
  • SSE:服务端单向推送(通知、AI 流式输出)

Q4: 心跳检测的作用?

答案

  1. 检测连接是否存活(客户端断网不会触发 close 事件)
  2. 防止中间设备(NAT、代理)关闭空闲连接
  3. 通常 30s 一次 ping/pong

相关链接