通常,心跳机制包括:
- 定期发送消息(如
ping
)到服务器以保持连接活跃 - 检测服务器的相应(
pong
) - 如果一段时间没有收到相应,可能需要重新连接
::: warning
在连接关闭或者发生错误时,清除心跳定时器,避免内存泄漏
:::
在实现时注意以下几点: - 心跳的发送间隔和超时时间应该可配置,方便调整
- 在连接成功后启动心跳,在连接关闭或出错时停止
- 收到
pong
相应时,更新最后一次收到消息的时间戳 - 设置一个检查机制,定期检查是否超时,如果超时则主动关闭连接并触发重连
2 代码实现
2.1 WebSocket的配置
const WEBSOCKET_CONFIG = {
HOST: "127.0.0.1:8081",
PATH_PREFIX: "/easyexcel/",
HEARTBEAT_INTERVAL: 25000, // 心跳发送间隔(ms)
HEARTBEAT_TIMEOUT: 30000, // 心跳超时时间
RECONNECT_BASE_INTERVAL: 1000, // 基础重连间隔
RECONNECT_MAX_ATTEMPTS: 5, // 最大重连尝试次数
RECONNECT_MULTIPLIER: 1.5 // 重连间隔倍数
};
2.2 创建WebSocket连接
//清理资源
this.cleanWebSocketResources();
// URL 换成你自己 WebSocket 服务的地址
this.ws = this.univerAPI.createSocket("ws://" + WEBSOCKET_CONFIG.HOST + WEBSOCKET_CONFIG.PATH_PREFIX + "/" + this.userName + "/" + id);
2.3 处理连接成功
this.ws.open$.subscribe(() => {
console.log("websocket opened");
this.ws.readyState = WebSocket.OPEN; //修改ws状态
this.startHeartBeat(); //启动心跳连接
this.startWatchDog(); //启动看门狗
this.resetReconnectAttemps(); //重置重连次数
});
- 启动心跳连接,定期发送
ping
消息
startHeartBeat() {
this.stopHeartBeat();
this.heartBeatTimer = setInterval(() => {
if(this.ws?.readyState === WebSocket.OPEN) {
this.ws.send("heartbeat");
}
}, WEBSOCKET_CONFIG.HEARTBEAT_INTERVAL);
},
- 启动开门狗,定时检查最新消息时间是否大于设置的消息超时时间,如果超时则主动关闭连接
startWatchDog() {
this.stopWatchDoger();
this.watchdogTimer = setInterval(() => {
let timeSinceLastMessage = Date.now() - this.lastMessageTime; //最新消息间隔时间
if(timeSinceLastMessage > WEBSOCKET_CONFIG.HEARTBEAT_TIMEOUT) {
console.error("心跳超时");
this.ws?.close(4000, "HeartBeat Timeout");
}
}, WEBSOCKET_CONFIG.HEARTBEAT_TIMEOUT / 2);
},
2.4 处理消息接收
- 在接收到消息时,更新最新消息时间
this.ws.message$.subscribe((message) => {
console.log("websocket message", message.data);
this.lastMessageTime = Date.now(); //更新最新消息接收时间
const content = message.data;
//处理自己的逻辑
});
2.5 处理异常
this.ws.error$.subscribe((error) => {
console.log("websocket error", error);
this.ws.readyState = WebSocket.CLOSED;
this.scheduleReconnect();
});
- 当有异常时,需要重试连接
/**
* 重试连接
*/
scheduleReconnect() {
if(this.reconnectAttempts >= WEBSOCKET_CONFIG.RECONNECT_MAX_ATTEMPTS) {
console.error("已达最大重试连接次数,停止尝试");
return;
}
//指数退避策略:重连间隔 = 基础间隔 * (倍数^尝试次数)
const delay = WEBSOCKET_CONFIG.RECONNECT_BASE_INTERVAL * Math.pow(WEBSOCKET_CONFIG.RECONNECT_MULTIPLIER, this.reconnectAttempts);
//添加正负20%之间的随机抖动
const jitter = delay * 0.2 * (Math.random() * 2 - 1);
//最大间隔30s
const finalDealy = Math.max(delay + jitter, 30000);
//重试次数+1
this.reconnectAttempts++;
console.log(`将在${finalDealy}ms后进行第${this.reconnectAttempts}次重连`);
this.reconnectTimer = setTimeout(() => {
this.initWebSocket(this.document.id);
}, finalDealy);
},
指数退避策略
指数退避策略 是一种网络请求重连的智能算法,其核心公式:重连间隔=基础间隔×(倍数^尝试次数)
分步解释(以基础间隔1秒、倍数2为例)
重连次数 | 计算公式 | 实际等待时间 | 直观表现 |
---|---|---|---|
第1次重试 | 1 × (2⁰) | 1秒 | 立即尝试 |
第2次重试 | 1 × (2¹) | 2秒 | 稍等片刻 |
第3次重试 | 1 × (2²) | 4秒 | 明显延迟 |
第4次重试 | 1 × (2³) | 8秒 | 较长等待 |
第5次重试 | 1 × (2⁴) | 16秒 | 显著间隔 |
为什么要这样设计?
- 避免雪崩效应
如果所有客户端在断连后立即重试,可能导致服务端瞬间过载
指数延迟分散请求压力(类似TCP拥塞控制) - 平衡用户体验
前几次快速重试:应对短暂网络抖动
后续逐步延长:减少无效尝试,节省设备资源 - 自适应网络状态
重试次数越多 → 间隔越长 → 假设网络问题越严重
网络恢复后,新连接会重置计数器
::: tip
可以添加正负20%的随机波动,避免多个客户端同步尝试
const jitter = delay * 0.2 * (Math.random() * 2 - 1);
const finalDelay = delay + jitter;
:::