class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.ws = null; // WebSocket实例
    this.isConnected = false; // 连接状态
    this.reconnectTimer = null; // 重连定时器
    this.heartbeatTimer = null; // 心跳定时器
    this.reconnectCount = 0; // 重连次数
    this.maxReconnectCount = options.maxReconnectCount || 10; // 最大重连次数
    this.minReconnectInterval = options.minReconnectInterval || 1000; // 最小重连间隔(ms)
    this.maxReconnectInterval = options.maxReconnectInterval || 30000; // 最大重连间隔(ms)
    this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔(ms)
    this.heartbeatTimeout = options.heartbeatTimeout || 10000; // 心跳超时时间(ms)
    this.heartbeatCheckTimer = null; // 心跳检测定时器
    
    // 事件回调
    this.onOpen = options.onOpen || (() => {});
    this.onMessage = options.onMessage || (() => {});
    this.onClose = options.onClose || (() => {});
    this.onError = options.onError || (() => {});
    this.onReconnect = options.onReconnect || (() => {});
    
    // 初始化连接
    this.connect();
  }
  
  /**
   * 建立WebSocket连接
   */
  connect() {
    // 如果已经连接则关闭现有连接
    if (this.ws) {
      this.ws.close(1000, '手动重连');
    }
    
    try {
      this.ws = new WebSocket(this.url);
      
      // 连接成功回调
      this.ws.onopen = (event) => {
        this.isConnected = true;
        this.reconnectCount = 0; // 重置重连次数
        clearTimeout(this.reconnectTimer);
        
        // 启动心跳
        this.startHeartbeat();
        
        // 触发打开事件
        this.onOpen(event);
      };
      
      // 接收消息回调
      this.ws.onmessage = (event) => {
        // 如果是心跳响应,重置心跳检测
        if (event.data === 'pong') {
          this.resetHeartbeatCheck();
          return;
        }
        
        // 触发消息事件
        this.onMessage(event);
      };
      
      // 连接关闭回调
      this.ws.onclose = (event) => {
        this.isConnected = false;
        clearTimeout(this.heartbeatTimer);
        clearTimeout(this.heartbeatCheckTimer);
        
        // 触发关闭事件
        this.onClose(event);
        
        // 如果不是正常关闭(1000),则尝试重连
        if (event.code !== 1000) {
          this.reconnect();
        }
      };
      
      // 错误回调
      this.ws.onerror = (error) => {
        this.onError(error);
      };
    } catch (error) {
      this.onError(error);
      this.reconnect();
    }
  }
  
  /**
   * 发送消息
   * @param {string|Object} data 要发送的数据
   */
  send(data) {
    if (!this.isConnected || !this.ws) {
      console.error('WebSocket未连接,无法发送消息');
      return false;
    }
    
    try {
      const sendData = typeof data === 'object' ? JSON.stringify(data) : data;
      this.ws.send(sendData);
      return true;
    } catch (error) {
      console.error('发送消息失败:', error);
      this.onError(error);
      return false;
    }
  }
  
  /**
   * 启动心跳机制
   */
  startHeartbeat() {
    // 清除现有定时器
    clearTimeout(this.heartbeatTimer);
    clearTimeout(this.heartbeatCheckTimer);
    
    // 定时发送心跳包
    const sendHeartbeat = () => {
      if (this.isConnected) {
        this.send('ping');
        // 启动心跳检测
        this.startHeartbeatCheck();
      }
      this.heartbeatTimer = setTimeout(sendHeartbeat, this.heartbeatInterval);
    };
    
    // 立即发送第一个心跳
    sendHeartbeat();
  }
  
  /**
   * 启动心跳检测
   */
  startHeartbeatCheck() {
    clearTimeout(this.heartbeatCheckTimer);
    this.heartbeatCheckTimer = setTimeout(() => {
      // 心跳超时,关闭连接触发重连
      console.warn('WebSocket心跳超时,准备重连');
      this.ws.close(1006, '心跳超时');
    }, this.heartbeatTimeout);
  }
  
  /**
   * 重置心跳检测
   */
  resetHeartbeatCheck() {
    clearTimeout(this.heartbeatCheckTimer);
  }
  
  /**
   * 重连机制
   */
  reconnect() {
    // 如果已达到最大重连次数,停止重连
    if (this.reconnectCount >= this.maxReconnectCount) {
      console.error(`已达到最大重连次数(${this.maxReconnectCount}),停止重连`);
      return;
    }
    
    // 计算重连间隔(指数退避策略)
    const exponent = Math.min(this.reconnectCount, 10); // 限制指数增长
    const interval = Math.min(
      this.minReconnectInterval * Math.pow(2, exponent),
      this.maxReconnectInterval
    );
    
    // 延迟重连
    this.reconnectTimer = setTimeout(() => {
      this.reconnectCount++;
      console.log(`正在进行第${this.reconnectCount}次重连...`);
      this.onReconnect(this.reconnectCount, interval);
      this.connect();
    }, interval);
  }
  
  /**
   * 手动关闭连接
   */
  close() {
    if (this.ws) {
      this.ws.close(1000, '手动关闭');
    }
    clearTimeout(this.reconnectTimer);
    clearTimeout(this.heartbeatTimer);
    clearTimeout(this.heartbeatCheckTimer);
    this.isConnected = false;
  }
}

// 使用示例
// const wsClient = new WebSocketClient('wss://your-websocket-server.com', {
//   maxReconnectCount: 15,
//   heartbeatInterval: 20000,
//   onOpen: (event) => {
//     console.log('WebSocket连接成功');
//   },
//   onMessage: (event) => {
//     console.log('收到消息:', event.data);
//   },
//   onClose: (event) => {
//     console.log('WebSocket连接关闭:', event);
//   },
//   onError: (error) => {
//     console.error('WebSocket错误:', error);
//   },
//   onReconnect: (count, interval) => {
//     console.log(`准备进行第${count}次重连,间隔${interval}ms`);
//   }
// });
// 
// // 发送消息
// wsClient.send({ type: 'chat', content: 'Hello WebSocket' });

实现说明

这个 WebSocket 客户端工具类提供了完整的连接管理功能,主要特点包括:

  1. 核心功能

    • 基础的 WebSocket 连接建立与消息发送
    • 自动重连机制(断开后自动尝试重新连接)
    • 心跳检测(保持连接活跃,检测连接有效性)
  2. 重连策略

    • 采用指数退避算法,重连间隔逐渐增加(1s → 2s → 4s → ... 最大 30s)
    • 可配置最大重连次数,避免无限重连
    • 异常关闭时自动触发重连,正常关闭则不重连
  3. 心跳机制

    • 定期发送 "ping" 心跳包(默认 30 秒一次)
    • 等待服务器返回 "pong" 响应(超时时间默认 10 秒)
    • 心跳超时则判定连接失效,触发重连
  4. 事件回调

    • 提供 onOpen、onMessage、onClose、onError 等基础事件
    • 增加 onReconnect 事件,方便跟踪重连状态

使用方法

  1. 实例化 WebSocketClient 并传入服务器地址和配置项
  2. 通过 onMessage 回调处理接收到的消息
  3. 使用 send 方法发送消息
  4. 必要时调用 close 方法手动关闭连接

注意事项

  • 服务器端需要配合实现心跳响应(收到 "ping" 返回 "pong")
  • 根据实际需求调整心跳间隔和超时时间
  • 重连次数和间隔可根据网络环境灵活配置
  • 生产环境中建议增加日志记录,方便排查连接问题

这种实现方案能够有效应对网络波动、服务器短暂不可用等情况,大幅提高 WebSocket 连接的稳定性。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐