WebSocket 实战:从频繁断连到稳定连接
我们的实时聊天应用,用户经常抱怨消息丢失、连接断开。
经过一系列优化,连接稳定性从 85% 提升到 99.9%。
问题背景
用户反馈
CODE
"发消息经常发不出去"
"页面放一会儿就断开了"
"切换 WiFi 后连接丢失"
"后台运行后无法恢复"监控数据
| 指标 | 数值 |
|------|------|
| 连接成功率 | 85% |
| 平均断连次数 | 3.2 次/小时 |
| 消息丢失率 | 2.1% |
| 重连成功率 | 60% |
问题分析
问题 1:没有心跳机制
JAVASCRIPT
// ❌ 没有心跳
const ws = new WebSocket('wss://api.example.com/ws');
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 连接可能在用户不知情的情况下断开问题 2:重连逻辑简单
JAVASCRIPT
// ❌ 简单重连
ws.onclose = () => {
// 立即重连,可能失败
setTimeout(() => {
ws = new WebSocket('wss://api.example.com/ws');
}, 1000);
};问题 3:没有消息确认
JAVASCRIPT
// ❌ 发送后不确认
ws.send(JSON.stringify({ type: 'message', content: 'Hello' }));
// 不知道消息是否到达解决方案
方案 1:心跳机制
JAVASCRIPT
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.heartbeatInterval = null;
this.heartbeatTimeout = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('连接成功');
this.reconnectAttempts = 0;
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// 收到心跳响应
if (data.type === 'pong') {
this.resetHeartbeatTimeout();
return;
}
// 处理业务消息
this.handleMessage(data);
};
this.ws.onclose = (event) => {
console.log('连接关闭:', event.code, event.reason);
this.stopHeartbeat();
this.reconnect();
};
this.ws.onerror = (error) => {
console.error('连接错误:', error);
};
}
startHeartbeat() {
// 每 30 秒发送心跳
this.heartbeatInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
// 设置超时
this.heartbeatTimeout = setTimeout(() => {
console.log('心跳超时,关闭连接');
this.ws.close();
}, 10000);
}
}, 30000);
}
resetHeartbeatTimeout() {
if (this.heartbeatTimeout) {
clearTimeout(this.heartbeatTimeout);
this.heartbeatTimeout = null;
}
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
this.resetHeartbeatTimeout();
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('达到最大重连次数');
return;
}
// 指数退避
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
console.log(`${delay}ms 后重连...`);
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, delay);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn('连接未就绪,消息丢失:', data);
}
}
handleMessage(data) {
// 处理业务消息
console.log('收到消息:', data);
}
}
// 使用
const client = new WebSocketClient('wss://api.example.com/ws');
client.connect();方案 2:消息确认机制
JAVASCRIPT
class ReliableWebSocket extends WebSocketClient {
constructor(url) {
super(url);
this.pendingMessages = new Map();
this.messageId = 0;
}
send(data, requireAck = true) {
const message = {
...data,
_id: ++this.messageId,
_timestamp: Date.now()
};
if (requireAck) {
// 存储待确认消息
this.pendingMessages.set(message._id, {
data: message,
timestamp: Date.now(),
retries: 0
});
// 设置超时重发
setTimeout(() => {
this.checkMessageAck(message._id);
}, 5000);
}
super.send(message);
}
checkMessageAck(messageId) {
const pending = this.pendingMessages.get(messageId);
if (!pending) return;
if (pending.retries >= 3) {
console.error('消息发送失败:', pending.data);
this.pendingMessages.delete(messageId);
return;
}
// 重发
pending.retries++;
console.log(`重发消息 ${messageId},第 ${pending.retries} 次`);
super.send(pending.data);
// 再次检查
setTimeout(() => {
this.checkMessageAck(messageId);
}, 5000);
}
handleMessage(data) {
// 处理确认
if (data.type === 'ack') {
this.pendingMessages.delete(data.messageId);
return;
}
// 处理业务消息
super.handleMessage(data);
// 发送确认
if (data._id) {
super.send({ type: 'ack', messageId: data._id });
}
}
}方案 3:离线消息队列
JAVASCRIPT
class OfflineQueueWebSocket extends ReliableWebSocket {
constructor(url) {
super(url);
this.offlineQueue = [];
this.isOnline = false;
}
connect() {
super.connect();
// 监听网络状态
window.addEventListener('online', () => {
console.log('网络恢复');
this.isOnline = true;
this.flushQueue();
});
window.addEventListener('offline', () => {
console.log('网络断开');
this.isOnline = false;
});
}
send(data, requireAck = true) {
if (!this.isOnline || this.ws.readyState !== WebSocket.OPEN) {
// 离线时加入队列
this.offlineQueue.push({ data, requireAck });
console.log('消息加入离线队列:', data);
return;
}
super.send(data, requireAck);
}
flushQueue() {
console.log(`刷新离线队列,${this.offlineQueue.length} 条消息`);
while (this.offlineQueue.length > 0) {
const { data, requireAck } = this.offlineQueue.shift();
super.send(data, requireAck);
}
}
handleMessage(data) {
super.handleMessage(data);
// 连接成功后刷新队列
if (data.type === 'connected') {
this.isOnline = true;
this.flushQueue();
}
}
}服务端实现
Node.js + ws 库
JAVASCRIPT
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// 客户端连接
const clients = new Map();
wss.on('connection', (ws, req) => {
const clientId = generateClientId();
clients.set(clientId, ws);
console.log(`客户端连接: ${clientId}`);
// 发送连接确认
ws.send(JSON.stringify({
type: 'connected',
clientId,
timestamp: Date.now()
}));
// 心跳响应
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'ping') {
ws.send(JSON.stringify({ type: 'pong' }));
return;
}
// 发送确认
if (data._id) {
ws.send(JSON.stringify({
type: 'ack',
messageId: data._id
}));
}
// 处理业务消息
handleMessage(clientId, data);
});
ws.on('close', () => {
console.log(`客户端断开: ${clientId}`);
clients.delete(clientId);
});
ws.on('error', (error) => {
console.error(`客户端错误: ${clientId}`, error);
});
});
function handleMessage(clientId, data) {
// 处理业务逻辑
console.log(`收到消息 from ${clientId}:`, data);
// 广播给其他客户端
clients.forEach((client, id) => {
if (id !== clientId && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
}
// 定期检查连接健康
setInterval(() => {
clients.forEach((ws, clientId) => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
} else {
clients.delete(clientId);
}
});
}, 30000);最佳实践
1. 连接状态管理
JAVASCRIPT
// ✅ 使用状态机管理连接
const ConnectionState = {
CONNECTING: 'connecting',
CONNECTED: 'connected',
DISCONNECTED: 'disconnected',
RECONNECTING: 'reconnecting'
};
class StateMachineWebSocket {
constructor(url) {
this.url = url;
this.state = ConnectionState.DISCONNECTED;
}
connect() {
if (this.state === ConnectionState.CONNECTED) {
return;
}
this.state = ConnectionState.CONNECTING;
// ... 连接逻辑
}
onOpen() {
this.state = ConnectionState.CONNECTED;
}
onClose() {
this.state = ConnectionState.RECONNECTING;
// ... 重连逻辑
}
}2. 错误处理
JAVASCRIPT
// ✅ 完善的错误处理
ws.onerror = (event) => {
console.error('WebSocket 错误:', event);
// 根据错误类型处理
if (event.code === 1006) {
// 异常关闭,需要重连
this.reconnect();
} else if (event.code === 1008) {
// 策略违规,可能需要重新认证
this.reauthenticate();
}
};3. 资源清理
JAVASCRIPT
// ✅ 组件卸载时清理
useEffect(() => {
const ws = new WebSocket(url);
return () => {
// 清理
ws.close();
clearInterval(heartbeatInterval);
clearTimeout(reconnectTimeout);
};
}, [url]);效果对比
| 指标 | 优化前 | 优化后 |
|------|--------|--------|
| 连接成功率 | 85% | 99.9% |
| 平均断连次数 | 3.2 次/小时 | 0.1 次/小时 |
| 消息丢失率 | 2.1% | 0.01% |
| 重连成功率 | 60% | 95% |
总结
WebSocket 稳定性的核心原则:
1. 心跳机制:定期检测连接状态
2. 指数退避重连:避免频繁重连
3. 消息确认:确保消息到达
4. 离线队列:网络恢复后自动发送
5. 状态管理:清晰的状态机设计
做好这些,连接稳定性可以达到 99.9%。
优化时间:2026年7月
应用类型:实时聊天
连接稳定性:85% → 99.9%
#WebSocket #实时通信 #连接稳定性
读者评论 3