WebSocket断线后自动重连机制如何实现?重连时旧连接未关闭导致连接爆炸怎么办?
我在开发聊天功能时用WebSocket做实时通信,写了个自动重连逻辑。但发现网络波动时会出现多个连接实例同时存在,服务端返回403错误,控制台提示”WebSocket is already in CLOSING or CLOSED state”。
尝试过用指数退避算法重连,但代码里明明加了close()方法,断开调试时还是能看到多个连接在后台堆积。这是不是因为旧连接没彻底关闭?我的代码逻辑是这样的:
let ws = new WebSocket('wss://chat.example.com');
let retryCount = 0;
function connect() {
ws.onerror = () => ws.close();
ws.onclose = () => {
setTimeout(() => {
ws = new WebSocket('wss://chat.example.com'); // 这里是不是每次重连都新建实例?
retryCount++;
}, 2 ** retryCount * 1000);
};
}
测试时发现当retryCount到3次后,服务端开始拦截新连接,但之前的连接都没被销毁。应该在onclose里加什么判断条件才能确保旧连接完全关闭后再创建新连接呢?
先说说你代码的问题在哪:
你每次在onclose里新建WebSocket实例,但这个新建的实例没有重新绑定onclose、onopen、onmessage等回调方法。所以新连接一旦断开,onclose回调其实是空的,不会触发任何重连逻辑。而且你用
ws = new WebSocket()只是改变了变量引用,旧的ws对象还在内存里,它的连接状态可能还是OPEN或CLOSING,浏览器不会自动销毁它。这就是为什么会出现连接堆积。
正确的做法是:用一个状态标志来控制重连流程,确保同一时间只有一个连接存在,并且在创建新连接前先清理旧连接。
具体来说这样做:
关键点解释一下:
第一,
isConnecting这个标志很重要。WebSocket从创建到连接成功有个过程,如果你在这期间多次调用connect(),会创建多个实例。这个标志确保只有第一个调用真正创建连接。第二,
ws.readyState的判断不能少。你原来的代码在onerror里直接调用ws.close(),但如果连接已经处于CLOSING或CLOSED状态,再调用close()就会报"WebSocket is already in CLOSING or CLOSED state"这个错误。提前判断状态可以避免这个问题。第三,onerror里不要手动调用ws.close()。让连接出错后自然触发onclose,然后在onclose里统一处理重连逻辑。这样逻辑更清晰,也避免了状态判断的问题。
第四,服务端返回403说明它检测到了异常连接行为,可能是因为短时间内大量连接请求。你现在的指数退避算法已经能缓解这个问题,但第一次重试的延迟是1秒(2的0次方乘1000),如果觉得太频繁,可以改成从2秒或3秒开始。
第五,如果你的应用可能频繁切换网络(比如移动端),可以考虑监听online/offline事件,网络恢复时主动调用connect()。
好了,核心问题就是这些。你先把代码改一改,应该能解决连接堆积的问题。
ws.close()并不会立刻关闭连接,而是进入CLOSING状态;另一个是你在onclose里直接新建了 WebSocket 实例,没有确保旧连接彻底销毁。我的血泪教训是,WebSocket 的状态管理一定要严谨,不能盲目依赖
onclose或onerror。以下是我的解决方案:首先,在新建 WebSocket 实例之前,必须确认当前的连接已经彻底关闭。可以用一个标志位来跟踪连接状态,比如
isConnecting,防止重复创建连接。其次,清理掉旧的事件监听器,避免回调函数被多次触发。最后,用指数退避算法控制重连频率是个好思路,但要加上最大重试次数限制,不然服务端会因为过多无效连接直接拒绝。这是我重构后的代码逻辑:
重点来了:
1. 加了一个
isConnecting标志位,防止重复调用connect方法。2. 在
onclose和onerror里都调用了ws.close(),确保进入关闭流程。3. 设置了最大重试次数
maxRetryCount,避免无限重连拖垮服务端。另外,调试时建议你在服务端打印日志,观察每个连接的生命周期。有时候客户端看着像是断开了,但服务端可能还在维持连接,这种情况也会导致连接堆积。
总之,WebSocket 这种长连接的管理真的很头疼,稍不注意就会炸。希望我的经验能帮你少踩点坑。