前言
HTTP请求的本质是"问一次答一次",但很多场景需要服务端主动推送数据------比如消息通知、实时数据大屏、在线协作编辑。WebSocket就是解决这类问题的最佳方案。
这篇文章带你从原理到落地,快速掌握WebSocket实战技巧。
一、WebSocket vs HTTP
HTTP(短连接):
客户端发请求 → 服务端响应 → 连接断开
缺点:无法服务端主动推送,轮询浪费资源
WebSocket(长连接):
客户端发起握手 → 连接建立 → 双向实时通信
优点:低延迟、低开销、全双工通信
适用场景:
✅ 聊天系统
✅ 实时通知
✅ 股票/行情数据
✅ 在线协作(文档、白板)
✅ 游戏状态同步
二、Node.js服务端实现
2.1 基础WebSocket服务
javascript
// server.js
const 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.js
class 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
// websocket-client.js
class 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.2 水平扩展(多节点问题)
问题:多台服务器时,用户A在Node1,用户B在Node2
A给B发消息,Node1找不到B的连接!
解决方案:使用Redis Pub/Sub同步消息
Node1 收到A的消息
→ 发布到Redis频道
→ Node2 订阅到消息
→ Node2 找到B的连接并推送
javascript
const 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));
}
});
// 发送时发布到Redis
function 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值得每位后端工程师深入掌握。

