Skip to content

WebSocket、断线重连与消息队列

WebSocket 介绍

Websocket 是一种在单个 TCP 连接上提供全双工通信的协议。它由 HTML5 引入,旨在代替传统的轮询或长轮询方式,实现更高效的实时数据传输。

工作流程:

  1. 握手阶段:使用 HTTP 协议进行初始握手,随后将协议升级为 WebSocket 协议。
    • 客户端发送 Upgrade 请求头
    • 服务器响应 101 Switching Protocols 状态码,完成协议切换。
  2. 数据传输阶段:客户端和服务器可以任意方向发送消息,无需再通过 HTTP 请求头部信息,降低了通信开销。

特点:

  1. 全双工通信:客户端和服务器可以实时地相互发送消息。
  2. 低延迟:相比 HTTP 轮询,WebSocket 避免了频繁建立和断开连接,显著降低了延迟。
  3. 长连接:一次连接建立后可持续使用,减少了网络资源的消耗。
js
class WebSocketManager {
  constructor(url, maxRetries = 5) {
    this.url = url;
    this.socket = null;
    this.messageQueue = []; // 消息队列
    this.isConnected = false;
    this.reconnectAttempts = 0; // 当前重连次数
    this.maxRetries = maxRetries; // 最大重连次数

    this.init();
  }

  // 初始化
  init() {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      console.log('WebSocket connected!');
      this.isConnected = true;
      this.reconnectAttempts = 0; // 重连成功后重置计数
      this.flushQueue(); // 重连后发送消息队列中的消息
    };

    this.socket.onclose = () => {
      console.warn('WebSocket disconnect!');
      this.isConnected = false;
      this.reconnect(); // 尝试重连
    };
  }

  reconnect() {
    if (this.reconnectAttempts >= this.maxRetries) {
      console.error(
        'Max reconnect attempts reached. Please check your network.'
      );
      return; // 达到最大重连次数后停止重连
    }

    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); // 指数退避,最大 30 秒
    this.reconnectAttempts++;

    console.log(
      `Reconnecting WebSocket in ${delay / 1000} seconds... (Attempt ${
        this.reconnectAttempts
      }/${this.maxRetries})`
    );

    setTimeout(() => {
      this.init(); // 尝试重新初始化 WebSocket
    }, delay);
  }

  // 发送消息
  sendMessage(message) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      try {
        this.socket.send(message);
        console.log('Message sent:', message);
      } catch (error) {
        console.error('Error sending message:', error);
        this.messageQueue.push(message);
      }
    } else {
      console.warn('Connection lost, message queued:', message);
      this.messageQueue.push(message); // 入队
    }
  }

  // 清空队列
  flushQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift(); // 从队列中取出消息
      this.sendMessage(message); // 重发消息
    }
  }
}

// 使用示例
const wsManager = new WebSocketManager('wss://echo.websocket.org', 3);

// 模拟发送消息
wsManager.sendMessage('Hello, Server!');
wsManager.sendMessage('Another message');

// 模拟断线后重连,未发送的消息会在连接恢复后自动重发
setTimeout(async () => {
  await wsManager.socket.close();
  wsManager.sendMessage('after close');
}, 3000);