【IM】如何封装一个好用的功能全面的 `WebSocket` 客户端类?

634 阅读3分钟

【IM】如何封装一个好用的功能全面的 WebSocket 客户端类?

直接上代码:

class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.ws = null;
    this.reconnectInterval = options.reconnectInterval || 5000; // 初始重连间隔时间(毫秒)
    this.maxReconnectInterval = options.maxReconnectInterval || 60000; // 最大重连间隔时间(毫秒)
    this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔时间(毫秒)
    this.heartbeatTimeout = options.heartbeatTimeout || 10000; // 心跳超时时间(毫秒)
    this.maxReconnectAttempts = options.maxReconnectAttempts || 5; // 最大重连次数
    this.maxHeartbeatTimeouts = options.maxHeartbeatTimeouts || 3; // 最大心跳超时次数
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.isReconnecting = false;
    this.reconnectAttempts = 0;
    this.heartbeatTimeoutCount = 0; // 心跳超时计数器
    this.messageQueue = [];
    this.onOpen = options.onOpen || (() => {});
    this.onMessage = options.onMessage || (() => {});
    this.onClose = options.onClose || (() => {});
    this.onError = options.onError || (() => {});
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log("WebSocket 连接已打开");
      this.isReconnecting = false;
      this.reconnectAttempts = 0;
      this.heartbeatTimeoutCount = 0; // 重置心跳超时计数器
      this.startHeartbeat();
      this.flushMessageQueue();
      this.onOpen();
    };

    this.ws.onmessage = (event) => {
      console.log("收到消息:", event.data);
      if (event.data === JSON.stringify({ type: "heartbeat" })) {
        this.resetHeartbeatTimeout();
      }
      this.onMessage(event);
    };

    this.ws.onclose = (event) => {
      console.log("WebSocket 连接已关闭", event.code, event.reason);
      this.stopHeartbeat();
      this.onClose(event);
      if (
        !this.isReconnecting &&
        event.code !== 1000 &&
        event.code !== 1001 &&
        this.reconnectAttempts < this.maxReconnectAttempts
      ) {
        this.reconnect();
      }
    };

    this.ws.onerror = (error) => {
      console.error("WebSocket 错误:", error);
      this.onError(error);
    };
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: "heartbeat" }));
        this.setHeartbeatTimeout();
      }
    }, this.heartbeatInterval);
  }

  stopHeartbeat() {
    clearInterval(this.heartbeatTimer);
    clearTimeout(this.heartbeatTimeoutId);
  }

  setHeartbeatTimeout() {
    clearTimeout(this.heartbeatTimeoutId);
    this.heartbeatTimeoutId = setTimeout(() => {
      console.log("心跳超时,增加超时计数器");
      this.heartbeatTimeoutCount++;
      if (this.heartbeatTimeoutCount >= this.maxHeartbeatTimeouts) {
        console.log("达到最大心跳超时次数,尝试重新连接");
        this.ws.close();
      } else {
        this.setHeartbeatTimeout(); // 重新设置超时定时器
      }
    }, this.heartbeatTimeout);
  }

  resetHeartbeatTimeout() {
    this.heartbeatTimeoutCount = 0; // 重置心跳超时计数器
    this.setHeartbeatTimeout();
  }

  reconnect() {
    this.isReconnecting = true;
    const interval = Math.min(
      this.reconnectInterval * Math.pow(2, this.reconnectAttempts),
      this.maxReconnectInterval
    );
    this.reconnectTimer = setTimeout(() => {
      console.log(`尝试重新连接...(第 ${this.reconnectAttempts + 1} 次)`);
      this.reconnectAttempts++;
      this.connect();
    }, interval);
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    } else {
      this.messageQueue.push(data);
    }
  }

  flushMessageQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.ws.send(JSON.stringify(message));
    }
  }

  close() {
    this.ws.close(1000, "手动关闭");
    this.stopHeartbeat();
    clearTimeout(this.reconnectTimer);
  }
}

// 使用示例
const wsClient = new WebSocketClient("ws://example.com/socket", {
  onOpen: () => console.log("连接已打开"),
  onMessage: (event) => console.log("收到消息:", event.data),
  onClose: (event) => console.log("连接已关闭", event.code, event.reason),
  onError: (error) => console.error("连接错误:", error),
});

// 发送消息
wsClient.send({ type: "message", content: "Hello, Server!" });

// 关闭连接
// wsClient.close();

这个文件 ws.js 实现了一个功能完善的 WebSocket 客户端类 WebSocketClient,支持心跳机制、断线重连和心跳超时重连等功能。以下是该文件的主要功能描述:

主要功能

  1. WebSocket 连接管理

    • 连接建立:通过 connect 方法建立 WebSocket 连接。
    • 连接打开:在 ws.onopen 事件中处理连接打开后的逻辑,包括启动心跳机制和发送缓存的消息。
    • 连接关闭:在 ws.onclose 事件中处理连接关闭后的逻辑,包括根据关闭代码决定是否进行重连。
    • 连接错误:在 ws.onerror 事件中处理连接错误,并记录错误信息。
  2. 心跳机制

    • 发送心跳:通过 startHeartbeat 方法定期发送心跳消息。
    • 心跳超时:通过 setHeartbeatTimeout 方法设置心跳超时定时器,如果在指定时间内没有收到心跳响应,则增加超时计数器。
    • 重置心跳超时:在 resetHeartbeatTimeout 方法中重置心跳超时计数器,并重新设置超时定时器。
  3. 断线重连

    • 重连逻辑:在 reconnect 方法中实现重连逻辑,使用指数退避策略增加重连间隔时间。
    • 最大重连次数:通过 maxReconnectAttempts 控制最大重连次数,避免无限重连。
  4. 心跳超时重连

    • 心跳超时计数器:通过 heartbeatTimeoutCount 记录连续的心跳超时次数。
    • 最大心跳超时次数:通过 maxHeartbeatTimeouts 控制最大心跳超时次数,只有在达到指定次数时才进行重连。
  5. 消息发送与缓存

    • 发送消息:通过 send 方法发送消息,如果连接未打开,则将消息缓存到 messageQueue 中。
    • 消息队列:通过 flushMessageQueue 方法在连接打开后发送缓存的消息。
  6. 手动关闭连接

    • 关闭连接:通过 close 方法手动关闭 WebSocket 连接,并清理所有定时器和事件监听器。

使用示例

文件中提供了一个使用示例,展示了如何创建 WebSocketClient 实例并设置回调函数来处理连接打开、消息接收、连接关闭和错误事件。

const wsClient = new WebSocketClient("ws://example.com/socket", {
  onOpen: () => console.log("连接已打开"),
  onMessage: (event) => console.log("收到消息:", event.data),
  onClose: (event) => console.log("连接已关闭", event.code, event.reason),
  onError: (error) => console.error("连接错误:", error),
});

// 发送消息
wsClient.send({ type: "message", content: "Hello, Server!" });

// 关闭连接
// wsClient.close();

总结

这个文件实现了一个功能全面的 WebSocket 客户端类,能够处理常见的 WebSocket 连接问题,包括心跳机制、断线重连和心跳超时重连等,适用于需要稳定 WebSocket 连接的场景。