如何捕获并处理Node中WebSocket已处于CLOSING/CLOSED状态错误
解决WebSocket连接随机断开及重连的最优方案
我之前在使用ws库开发Node WebSocket服务时,也碰到过一模一样的随机断开问题,结合你的代码场景,整理了一套高效的检测+自动重连方案,分客户端和服务端两部分来处理:
一、客户端:实现智能重连+消息保障
1. 封装带重连逻辑的WebSocket客户端
核心思路是监听close、error事件触发重连,同时加入指数退避策略避免频繁重连给服务器造成压力:
class ReconnectingWebSocket { constructor(url) { this.url = url; this.socket = null; this.reconnectDelay = 1000; // 初始重连间隔1秒 this.maxReconnectDelay = 30000; // 最大重连间隔30秒 this.connect(); } connect() { this.socket = new WebSocket(this.url); this.socket.addEventListener('open', () => { console.log("Connected to server"); this.reconnectDelay = 1000; // 连接成功后重置重连间隔 }); this.socket.addEventListener('message', (event) => { console.log('Message from server ', event.data); // 这里可以添加你的业务消息处理逻辑 }); this.socket.addEventListener('close', () => { console.log('Connection closed, attempting reconnect...'); this.scheduleReconnect(); }); this.socket.addEventListener('error', (error) => { console.error('WebSocket error:', error); this.scheduleReconnect(); }); } scheduleReconnect() { setTimeout(() => { // 指数递增重连间隔,直到最大值 this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay); this.connect(); }, this.reconnectDelay); } // 封装安全的send方法,避免在断开状态下发消息报错 send(data) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(data)); } else { console.warn('WebSocket not open, message queued:', data); // 可选优化:如果需要保障消息不丢失,可以把消息存入队列,重连后批量发送 // this.messageQueue.push(data); } } } // 使用示例 const wsClient = new ReconnectingWebSocket('ws://localhost:3090'); // 发送消息时调用封装的send方法 // wsClient.send({ type: 'test', content: 'hello server' });
2. 加入心跳机制,提前检测死连接
大部分随机断开是因为服务器/网关回收了长时间无交互的连接,通过心跳可以主动维持连接,提前发现死连接:
在上面的ReconnectingWebSocket类中添加心跳逻辑:
class ReconnectingWebSocket { constructor(url) { // ... 原有代码 this.heartbeatInterval = null; this.heartbeatTimeout = 5000; // 5秒没收到pong就判定连接失效 } connect() { this.socket = new WebSocket(this.url); this.socket.addEventListener('open', () => { // ... 原有代码 this.startHeartbeat(); }); this.socket.addEventListener('close', () => { // ... 原有代码 this.stopHeartbeat(); }); this.socket.addEventListener('message', (event) => { // 优先处理心跳回复 if (event.data === 'pong') { clearTimeout(this.heartbeatTimeoutId); return; } // 处理业务消息 console.log('Message from server ', event.data); }); } startHeartbeat() { // 每10秒发送一次ping this.heartbeatInterval = setInterval(() => { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send('ping'); // 设置超时,没收到pong就主动断开重连 this.heartbeatTimeoutId = setTimeout(() => { console.log('Heartbeat timeout, closing connection...'); this.socket.close(); }, this.heartbeatTimeout); } }, 10000); } stopHeartbeat() { clearInterval(this.heartbeatInterval); clearTimeout(this.heartbeatTimeoutId); } // ... 原有send方法 }
二、服务端:配合心跳机制,主动清理死连接
在你的服务端代码中添加心跳响应和无响应连接清理逻辑:
const WebSocket = require('ws'); const wss = new WebSocket.Server({ server }); wss.on('connection', function connection(socket) { console.log('New client connected'); // 响应客户端的ping,返回pong socket.on('message', function incoming(data) { const message = data.toString(); if (message === 'ping') { socket.send('pong'); return; } console.log('Incoming data ', data); }); // 监听pong,超时未收到则主动断开连接 let heartbeatTimeout; socket.on('pong', () => { clearTimeout(heartbeatTimeout); heartbeatTimeout = setTimeout(() => { socket.terminate(); // 强制断开无响应的连接 }, 12000); // 12秒没收到pong就断开 }); // 连接建立后立即启动超时检测 heartbeatTimeout = setTimeout(() => { socket.terminate(); }, 12000); socket.on('close', () => { clearTimeout(heartbeatTimeout); console.log('Client disconnected'); }); });
三、关键注意事项
- 禁止直接调用原生
send:必须使用封装后的安全send方法,先判断readyState === WebSocket.OPEN,从根源避免你遇到的“WebSocket is already in CLOSING or CLOSED state”错误。 - 指数退避重连:不要用固定间隔重连,指数递增的间隔能平衡重连效率和服务器负载,避免网络波动时的无效重试。
- 心跳是核心解决方案:大部分随机断开都是因为无交互连接被回收,心跳能主动维持连接状态,提前发现并处理死连接。
内容的提问来源于stack exchange,提问作者adelriosantiago
相关产品推荐
相关产品推荐

