断线重连机制设计
断线重连是 WebSocket 应用的关键可靠性保障。核心问题有三个:什么情况下触发重连、重连间隔如何设定、什么时候停止重连。
重连触发条件
需要重连的情况:网络中断导致 onerror 或 onclose 触发。
不需要重连的情况:用户主动调用 close() 关闭连接。通过 closeState 标志位区分这两种情况。
private closeState: boolean = false
// 用户主动关闭
close() {
this.closeState = true
clearTimeout(this.pingTimeout)
clearTimeout(this.pongTimeout)
if (this.client) {
this.client.close()
}
}
// 重连前判断
private reconnect() {
if (this.closeState) return
if (!this.options.autoReconnect) return
// ... 执行重连逻辑
}
typescript
重连间隔策略
常见的重连间隔策略有三种:
| 策略 | 描述 | 适用场景 |
|---|---|---|
| 固定间隔 | 每次都等相同时间(如 3 秒) | 简单场景 |
| 递增间隔 | 每次重连间隔递增(2s, 4s, 8s...) | 通用场景 |
| 自定义策略 | 通过函数计算延迟 | 需要精细控制的场景 |
课程采用自定义策略 + 最大重试次数的方案:
interface WebSocketClientOptions {
// ...
retryStrategy?: (times: number) => number
maxRetries?: number
reconnectInterval?: number // 固定间隔的默认值,3000ms
}
private reconnecting: boolean = false
private retryCount: number = 0
private reconnect() {
if (this.closeState) return
if (!this.options.autoReconnect) return
if (this.reconnecting) return
this.reconnecting = true
this.retryCount++
// 计算延迟
const delay = this.getReconnectDelay()
// 检查是否超过最大重试次数
if (this.options.maxRetries && this.retryCount > this.options.maxRetries) {
this.close()
return
}
setTimeout(() => {
this.connect()
}, delay)
}
private getReconnectDelay(): number {
if (this.options.retryStrategy) {
return this.options.retryStrategy(this.retryCount)
}
return this.options.reconnectInterval || 3000
}
typescript
常用的 retryStrategy 示例
// 递增策略,上限 2 秒
const options = {
retryStrategy: (times: number) => Math.min(times * 50, 2000),
maxRetries: 24
}
typescript
这个策略让重连频率从快到慢:前几次重连间隔很短(50ms、100ms、150ms...),逐步增加到 2 秒的上限。超过 24 次重试后停止。这样设计的好处是:网络短暂波动时能快速恢复,持续断网时不会无节制地消耗资源。
消息缓存与重发
断线期间客户端发送的消息会丢失。解决方案是将断线期间的消息缓存在本地数组中,重连成功后批量发送:
private dataArr: any[] = []
send(data: any) {
// 只有连接正常时才发送
if (this.client && this.client.readyState === WebSocket.OPEN) {
// 先发送缓存中的消息
if (this.dataArr.length > 0) {
this.dataArr.forEach(item => {
this.client.send(this.formatData(item))
})
this.dataArr = []
}
// 再发送当前消息
this.client.send(this.formatData(data))
} else {
// 连接不可用时缓存消息
// 过滤掉心跳消息,只缓存业务消息
if (data && data.event && data.event === 'ping') return
this.dataArr.push(data)
}
}
private formatData(data: any): string {
if (typeof data === 'string') return data
return JSON.stringify(data)
}
typescript
需要注意一个边界情况:ping 消息不应该被缓存。如果断线期间多个 ping 消息被缓存,重连后批量发送多个无意义的健康检查消息是浪费的。
reconnecting 状态的复位
reconnecting 标志位用于防止重连逻辑被重复触发,但必须在合适的时机复位,否则重连只执行一次:
// 以下位置需要复位 reconnecting
// 1. 连接成功时
this.client.onopen = () => {
this.reconnecting = false // 复位
// ...
}
// 2. 重连错误时(让下一轮重连可以继续)
this.client.onerror = () => {
this.reconnecting = false // 复位
// ...
}
// 3. 重连后连接关闭时
this.client.onclose = () => {
this.reconnecting = false // 复位
this.reconnect()
}
typescript
完整测试流程
- 启动服务端和客户端,确认正常连接
- 强制关闭服务端(Ctrl+C),观察客户端日志:
- 打印
断线重连,开始重连 - 按策略间隔不断尝试重连
- 打印
- 重新启动服务端,观察客户端自动恢复连接
- 测试
maxRetries:设置较低的最大重试次数,确认超过后客户端停止重连并打印主动断开连接 - 测试消息缓存:断线期间发送消息,重连后确认消息成功送达
通过 console.log 在关键节点打印日志(断线记录、重连次数、连接成功),可以帮助理解整个生命周期。
↑