不能直接 new websocket() 重试,因浏览器并发限制易占满连接池引发安全错误,且旧实例未清理会导致 onmessage 错乱;需统一管理 ws 引用、手动 close、解绑事件、指数退避重连并监听 visibilitychange。

WebSocket连接断开后为什么不能直接 new WebSocket() 重试
因为浏览器对同一域名的 WebSocket 连接有并发限制(通常为6个),频繁 new WebSocket() 且未清理旧实例,会快速占满连接池,触发 SecurityError: Failed to construct 'WebSocket': The URL's origin is not equal to the document's origin 或静默失败。更关键的是,onclose 触发时,实例已进入 CLOSED 状态,再调用 close() 无效果,但若在重连逻辑里没清空引用,旧实例仍可能响应 onmessage 导致数据错乱。
实操建议:
- 每次新建连接前,先检查并手动
ws.close()当前实例(即使状态已是CLOSED,调用也安全) - 用一个变量(如
this.ws)统一持有当前连接,赋值前先清理旧引用 - 避免在
onopen中立即发送消息——此时服务端可能尚未完成 handshake 同步,建议加setTimeout(..., 0)或监听自定义就绪事件
如何控制重连间隔和退避策略
固定间隔重连(如每3秒试一次)在服务端宕机期间会造成大量无效请求;而指数退避(exponential backoff)能缓解压力,但需防止退避到过大值(如超过60秒)导致用户感知“卡死”。
实操建议:
- 初始延迟设为
1000ms,每次失败后翻倍,上限设为30000ms(30秒) - 连续失败5次后暂停重连,等待用户主动触发(如点击“重试”按钮),或弹出提示
- 记录重连次数和上一次失败时间,避免页面后台运行时(
visibilitychange触发)还持续重连 - 使用
setTimeout而非setInterval,确保前一次重连逻辑彻底结束才开始下一次计时
如何避免重复绑定 onmessage/onerror 导致事件堆积
如果每次重连都重新赋值 ws.onmessage = handler,但没清除旧连接的监听器,旧 ws 实例一旦意外收到帧(比如网络延迟导致的残包),就会触发已失效的 handler,造成内存泄漏或逻辑错乱。
实操建议:
- 所有事件监听必须绑定在当前有效的
this.ws实例上,且仅绑定一次 - 不要在重连函数内部重复写
ws.onmessage = ...;应提前定义好 handler 函数,在createConnection()内统一绑定 - 在
onclose或重连前,显式设ws.onmessage = null、ws.onerror = null,切断引用 - 若用
addEventListener,必须配合removeEventListener—— 注意传入的函数必须是同一个引用,不能用匿名函数
完整可运行的轻量封装示例(含自动重连)
以下代码不依赖任何库,只用原生 API,重点体现引用管理、退避控制和事件解绑:
class ReconnectWebSocket {
constructor(url, options = {}) {
this.url = url;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.reconnectAttempts = 0;
this.ws = null;
this.shouldReconnect = true;
this.onOpen = options.onOpen || (() => {});
this.onMessage = options.onMessage || (() => {});
this.onError = options.onError || (() => {});
this.onClose = options.onClose || (() => {});
this.connect();
}
connect() {
if (this.ws && this.ws.readyState {
this.reconnectAttempts = 0;
this.reconnectDelay = 1000;
this.onOpen(this.ws);
};
this.ws.onmessage = (e) => this.onMessage(e);
this.ws.onerror = (e) => {
this.onError(e);
this.cleanup();
this.scheduleReconnect();
};
this.ws.onclose = (e) => {
this.onClose(e);
this.cleanup();
if (this.shouldReconnect) {
this.scheduleReconnect();
}
};
}
cleanup() {
if (this.ws) {
this.ws.onmessage = null;
this.ws.onerror = null;
this.ws.onclose = null;
this.ws.onopen = null;
this.ws = null;
}
}
scheduleReconnect() {
if (!this.shouldReconnect) return;
this.reconnectAttempts++;
const delay = Math.min(this.reconnectDelay, this.maxReconnectDelay);
setTimeout(() => {
if (this.shouldReconnect) {
this.connect();
}
}, delay);
this.reconnectDelay *= 2;
}
close() {
this.shouldReconnect = false;
if (this.ws) {
this.ws.close();
this.cleanup();
}
}
}
// 使用:
const ws = new ReconnectWebSocket('wss://echo.websocket.org', {
onOpen: (ws) => console.log('Connected'),
onMessage: (e) => console.log('Received:', e.data),
onClose: (e) => console.log('Closed:', e.code)
});
真正容易被忽略的是:重连过程中用户切换标签页再切回,visibilitychange 事件不会自动触发重连恢复;如果业务要求强实时,得额外监听 document.visibilityState === 'visible' 并手动调用 ws.connect() —— 但注意别和已有重连定时器冲突。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











