WebSocket实战:构建实时消息推送系统

前言
HTTP请求的本质是"问一次答一次",但很多场景需要服务端主动推送数据——比如消息通知、实时数据大屏、在线协作编辑。WebSocket就是解决这类问题的最佳方案。
这篇文章带你从原理到落地,快速掌握WebSocket实战技巧。
一、WebSocket vs HTTP
HTTP(短连接): 客户端发请求 → 服务端响应 → 连接断开 缺点:无法服务端主动推送,轮询浪费资源WebSocket(长连接): 客户端发起握手 → 连接建立 → 双向实时通信 优点:低延迟、低开销、全双工通信适用场景: ✅ 聊天系统 ✅ 实时通知 ✅ 股票/行情数据 ✅ 在线协作(文档、白板) ✅ 游戏状态同步二、Node.js服务端实现
2.1 基础WebSocket服务
javascript// server.jsconst WebSocket = require('ws');const http = require('http');const server = http.createServer();const wss = new WebSocket.Server({ server });// 在线用户管理const clients = new Map();wss.on('connection', (ws, req) => { const userId = req.url.split('?userId=')[1]; clients.set(userId, ws); console.log(`用户 ${userId} 已连接,当前在线:${clients.size}`); // 接收消息 ws.on('message', (data) => { const message = JSON.parse(data); handleMessage(userId, message); }); // 断开连接 ws.on('close', () => { clients.delete(userId); console.log(`用户 ${userId} 已断开`); }); // 错误处理 ws.on('error', (err) => { console.error(`用户 ${userId} 连接异常:`, err.message); clients.delete(userId); }); // 心跳检测 ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; });});// 消息处理function handleMessage(fromUser, message) { switch (message.type) { case 'private_msg': sendToUser(message.toUser, { type: 'private_msg', from: fromUser, content: message.content, time: Date.now() }); break; case 'broadcast': broadcast({ type: 'broadcast', from: fromUser, content: message.content }, fromUser); break; }}// 发送给指定用户function sendToUser(userId, data) { const ws = clients.get(userId); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); }}// 广播给所有在线用户function broadcast(data, excludeUser = null) { clients.forEach((ws, userId) => { if (userId !== excludeUser && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } });}// 心跳检测(每30秒)const heartbeat = setInterval(() => { wss.clients.forEach((ws) => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); });}, 30000);server.listen(8080, () => { console.log('WebSocket服务启动:ws://localhost:8080');});2.2 房间分组(聊天室场景)
javascript// 房间管理const rooms = new Map();function joinRoom(userId, roomId) { if (!rooms.has(roomId)) { rooms.set(roomId, new Set()); } rooms.get(roomId).add(userId);}function leaveRoom(userId, roomId) { const room = rooms.get(roomId); if (room) { room.delete(userId); if (room.size === 0) rooms.delete(roomId); }}function broadcastToRoom(roomId, data, excludeUser = null) { const room = rooms.get(roomId); if (!room) return; room.forEach((userId) => { if (userId !== excludeUser) { sendToUser(userId, data); } });}// 消息处理中加入房间逻辑function handleMessage(fromUser, message) { switch (message.type) { case 'join_room': joinRoom(fromUser, message.roomId); broadcastToRoom(message.roomId, { type: 'system', content: `用户 ${fromUser} 加入了房间` }); break; case 'room_msg': broadcastToRoom(message.roomId, { type: 'room_msg', from: fromUser, content: message.content, time: Date.now() }, fromUser); break; case 'leave_room': leaveRoom(fromUser, message.roomId); break; }}三、前端客户端实现
3.1 封装WebSocket客户端
javascript// websocket-client.jsclass WSClient { constructor(url) { this.url = url; this.ws = null; this.listeners = new Map(); this.reconnectDelay = 3000; this.maxReconnect = 5; this.reconnectCount = 0; } connect(userId) { this.ws = new WebSocket(`${this.url}?userId=${userId}`); this.ws.onopen = () => { console.log('连接成功'); this.reconnectCount = 0; this.emit('connected'); }; this.ws.onmessage = (event) => { const message = JSON.parse(event.data); this.emit(message.type, message); }; this.ws.onclose = () => { console.log('连接断开,尝试重连...'); this.emit('disconnected'); this.reconnect(userId); }; this.ws.onerror = (err) => { console.error('连接异常:', err); }; } // 自动重连 reconnect(userId) { if (this.reconnectCount >= this.maxReconnect) { console.error('重连次数超限,请刷新页面'); return; } this.reconnectCount++; setTimeout(() => this.connect(userId), this.reconnectDelay); } // 发送消息 send(type, data) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type, ...data })); } } // 事件监听 on(event, callback) { this.listeners.set(event, callback); } emit(event, data) { const callback = this.listeners.get(event); if (callback) callback(data); } disconnect() { this.ws?.close(); }}// 使用示例const ws = new WSClient('ws://localhost:8080');ws.connect('user_001');ws.on('connected', () => { // 加入聊天室 ws.send('join_room', { roomId: 'room_001' });});ws.on('room_msg', (msg) => { console.log(`${msg.from}: ${msg.content}`);});ws.on('private_msg', (msg) => { console.log(`私信来自 ${msg.from}: ${msg.content}`);});// 发送消息ws.send('room_msg', { roomId: 'room_001', content: '大家好!'});四、生产环境注意事项
4.1 Nginx代理WebSocket
nginx# nginx.confserver { listen 443 ssl; server_name myapp.com; location /ws { proxy_pass http://localhost:8080; # WebSocket必要配置 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; # 超时设置(心跳间隔需小于此值) proxy_read_timeout 60s; proxy_send_timeout 60s; }}4.2 水平扩展(多节点问题)
问题:多台服务器时,用户A在Node1,用户B在Node2 A给B发消息,Node1找不到B的连接!解决方案:使用Redis Pub/Sub同步消息Node1 收到A的消息 → 发布到Redis频道 → Node2 订阅到消息 → Node2 找到B的连接并推送javascriptconst redis = require('redis');const pub = redis.createClient();const sub = redis.createClient();// 订阅跨节点消息sub.subscribe('ws_messages');sub.on('message', (channel, data) => { const { toUser, message } = JSON.parse(data); // 本节点有该用户则推送 const ws = clients.get(toUser); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(message)); }});// 发送时发布到Redisfunction sendToUser(userId, data) { const ws = clients.get(userId); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } else { // 本节点没有该用户,广播给其他节点 pub.publish('ws_messages', JSON.stringify({ toUser: userId, message: data })); }}五、团队与工具
我们团队在做实时协作功能时,产品、设计、前后端工程师每天都需要开跨时区的需求讨论会。会议中偶尔有英文表述不清楚的地方,我们会借助同言翻译(Transync AI)的实时语音翻译功能及时消除歧义,让沟通效率大幅提升。
六、性能优化要点
✅ 消息体积:尽量精简JSON结构,避免冗余字段✅ 心跳机制:防止连接假死,推荐30s间隔✅ 断线重连:指数退避策略,避免瞬间涌入✅ 消息队列:高并发时先入队再推送✅ 连接数限制:单节点建议不超过5万并发连接✅ 消息压缩:大消息启用permessage-deflate压缩总结
WebSocket是实时通信的首选方案,掌握核心要点:
- 服务端:连接管理 + 消息路由 + 心跳检测
- 客户端:自动重连 + 事件监听封装
- 生产环境:Nginx代理 + Redis集群同步
- 性能优化:控制消息体积 + 限制连接数
从一个简单的聊天室开始,逐步演进为支撑百万并发的实时系统,WebSocket值得每位后端工程师深入掌握。
文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有