You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获并处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:40:14