背景

在我做过的一个对接海康威视监控实现直播流的项目中,需要处理 WebSocket 的长连接,以便于和服务器保持实时通信。WebSocket 的一个挑战是连接的稳定性,特别是在网络波动或断开时,如何确保能自动重连并保持通信流畅。为了简化这一过程,我决定封装一个 WebSocket 类,使得开发者无需每次都重复编写连接、心跳、重连等逻辑。

这个 CustomSocket 类支持以下功能:

  1. 心跳机制:定时发送心跳包,确保连接不会因长时间无数据传输而被关闭。
  2. 断线重连:连接断开后,自动尝试重连,最大重连次数可配置。
  3. 手动连接:支持手动发起连接,适用于取消自动重连并重新启动连接。
  4. 消息回调:支持自定义处理接收到的消息,并且能够过滤心跳消息。
  5. 连接状态感知:可以实时监听 WebSocket 的连接状态,便于进行调试和优化。
  6. 调试日志:在 debug 模式下,可以输出详细的连接状态和操作日志,帮助调试和排查问题。

代码实现

仅此我项目够用了,便不封装太多避免太复杂繁琐,诸君有需要的可拿去更改。
以下是 CustomSocket 类的完整实现:

/**
 * WebSocket 封装类
 * 功能:自动重连、心跳机制、状态管理、消息回调
 */
const SOCKET_STATUS = {
  CONNECTING: "connecting", // 正在连接
  CONNECTED: "connected", // 已连接
  DISCONNECTED: "disconnected", // 已断开
  RECONNECTING: "reconnecting", // 正在重连
  CLOSED: "closed", // 已手动关闭
};

export class CustomSocket {
  /**
   * 构造函数
   * @param {string} url - WebSocket 服务器地址
   * @param {Object} options - 配置项
 // ===== 配置项 =====
   * @param {number} [options.heartbeatInterval=10000] - 心跳发送间隔(毫秒)
   * @param {number} [options.reconnectDelay=5000] - 重连延迟时间(毫秒)
   * @param {string} [options.pingMessage='ping'] - 发送心跳消息内容
   * @param {string} [options.pongMessage='pong'] - 响应心跳消息内容
   * @param {Function} [options.onMessage] - 接收到非心跳消息时的回调
   * @param {Function} [options.onOpen] - 连接成功时的回调
   * @param {Function} [options.onError] - 连接错误时的回调
   * @param {Function} [options.onClose] - 连接关闭时的回调
   * @param {Function} [options.onReconnect] - 尝试重连时的回调
   * @param {Function} [options.onStatusChange] - 任意状态变更时的回调(如 connected, disconnected, reconnecting, closed, connecting)
   * @param {Function} [options.onConnecting] - 启动连接前的回调(包括首次连接 & 重连)
   * @param {boolean} [options.debug=false] - 是否打印调试日志
   * @param {number} [options.maxReconnectAttempts=Infinity] - 最大重连次数,默认无限
   */
  constructor(url, options = {}) {
    this.url = url; // WebSocket 地址
    this.socket = null; // WebSocket 实例

    // 默认配置项
    this.heartbeatInterval = options.heartbeatInterval || 10000; // 心跳间隔时间,默认 10 秒
    this.reconnectDelay = options.reconnectDelay || 5000; // 重连等待时间,默认 5 秒
    this.pingMessage = options.pingMessage || "ping"; // 发送心跳消息内容
    this.pongMessage = options.pongMessage || "pong"; // 响应心跳消息内容
    this.debug = options.debug || false; // 是否启用调试日志
    this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity; // 最大重连次数

    // ===== 回调函数(支持注入)=====
    const noop = () => {};
    this.onMessage = options.onMessage || noop; // 消息接收回调
    this.onOpen = options.onOpen || noop; // 连接开启回调
    this.onError = options.onError || noop; // 连接错误回调
    this.onClose = options.onClose || noop; // 连接关闭回调
    this.onReconnect = options.onReconnect || noop; // 重连时回调
    this.onStatusChange = options.onStatusChange || noop; // 状态变更统一回调
    this.onConnecting = options.onConnecting || noop; // 连接开始回调

    // 状态字段
    this.isConnected = false; // 当前是否连接成功
    this.isReconnecting = false; // 当前是否正在重连
    this.manualClose = false; // 当前是手动关闭
    this.reconnectAttempts = 0; // 当前重连尝试次数
    this.lastPongTime = null; // 上一次收到 pong 的时间戳

    // 定时器句柄
    this.heartbeatTimer = null; // 心跳定时器
    this.reconnectTimer = null; // 重连定时器

    // 初始化连接
    this.init();
  }
  /**
   * 获取当前 WebSocket 连接状态(供外部状态感知使用)
   * @returns {string} - 当前状态
   */
  getStatus() {
    if (this.isConnected) return SOCKET_STATUS.CONNECTED;
    if (this.isReconnecting) return SOCKET_STATUS.RECONNECTING;
    if (this.socket && this.socket.readyState === WebSocket.CONNECTING)
      return SOCKET_STATUS.CONNECTING;
    return SOCKET_STATUS.DISCONNECTED;
  }

  /**
   * 初始化 WebSocket 连接并设置事件监听
   */
  init() {
    this.manualClose = false; // 每次新建连接都重置
    this.onStatusChange(SOCKET_STATUS.CONNECTING); // 新增:状态回调
    this.onConnecting(); // 页面可用来显示 loading
    this.socket = new WebSocket(this.url);

    // 连接成功
    this.socket.onopen = () => {
      this.isConnected = true;
      this.isReconnecting = false;
      this.reconnectAttempts = 0;
      this.debug && console.log("WebSocket opened.");
      this.onStatusChange("connected");
      this.onOpen();
      this.startHeartbeat();
    };

    // 接收消息
    this.socket.onmessage = (msg) => {
      // 处理 pong 响应(心跳回复)
      if (msg.data === this.pongMessage) {
        this.lastPongTime = Date.now(); // 记录收到心跳回应时间
        return; // 不传给业务逻辑
      }
      this.onMessage(msg); // 非心跳业务消息,转发给用户
    };

    // 连接出错
    this.socket.onerror = (err) => {
      this.debug && console.error("WebSocket error:", err);
      this.onError(err);
    };

    // 连接关闭
    this.socket.onclose = () => {
      this.debug && console.warn("WebSocket closed.");
      this.isConnected = false;
      this.stopHeartbeat();
      this.onClose();
      this.onStatusChange("disconnected");

      if (!this.manualClose) {
        this.debug &&
          console.warn(
            "WebSocket closed unexpectedly, attempting to reconnect..."
          );
        this.reconnect();
      } else {
        this.debug && console.log("WebSocket closed manually, no reconnect.");
      }
    };
  }

  /**
   * 手动连接(用于取消自动重连并立即发起连接)
   */
  manualConnect() {
    this.clearReconnectTimer(); // 清除已有的重连定时器
    this.isReconnecting = false; // 重置重连标记
    this.reconnectAttempts = 0; // 重置尝试次数
    this.manualClose = false; // 标记为非手动关闭
    this.init(); // 发起连接
  }
  /**
   * 启动心跳检测,定时发送心跳消息
   */
  startHeartbeat() {
    this.stopHeartbeat(); // 防止重复定时器
    this.heartbeatTimer = setInterval(() => {
      if (this.socket.readyState === WebSocket.OPEN) {
        this.socket.send(this.pingMessage);
        this.debug && console.log("Heartbeat sent:", this.pingMessage);
      }
    }, this.heartbeatInterval);
  }

  /**
   * 停止心跳检测
   */
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  /**
   * 重连逻辑(含延时与重试限制)
   */
  reconnect() {
    // 已在重连中则不重复触发
    if (this.reconnectTimer || this.isReconnecting) return;
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      this.debug &&
        console.warn("Max reconnect attempts reached, stopping reconnect.");
      this.clearReconnectTimer();
      return;
    }

    this.isReconnecting = true;
    this.reconnectAttempts++;
    this.onStatusChange(SOCKET_STATUS.RECONNECTING);
    this.onReconnect(); // 调用重连回调

    // 延迟执行重连
    this.reconnectTimer = setTimeout(() => {
      this.clearReconnectTimer();
      this.socket = null; // 销毁旧连接引用
      this.debug &&
        console.log(`Reconnecting... (Attempt ${this.reconnectAttempts})`);
      this.init();
    }, this.reconnectDelay);
  }

  /**
   * 清除重连定时器
   */
  clearReconnectTimer() {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }
  /**
   * 发送消息,如果是对象会自动序列化为 JSON 字符串
   * @param {string|Object} data - 要发送的数据
   */
  send(data) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      if (typeof data === "object") {
        data = JSON.stringify(data); // 如果是对象,自动序列化为 JSON 字符串
      }
      this.socket.send(data);
    } else {
      this.debug && console.warn("WebSocket 未连接,发送失败");
    }
  }

  
  /**
   * 主动关闭连接(将不再自动重连)
   */
  close() {
    this.manualClose = true; // 设置手动关闭标记
    this.stopHeartbeat();
    this.clearReconnectTimer();
    if (this.socket) {
      this.socket.close();
      this.socket = null;
    }
    this.isConnected = false;
    this.isReconnecting = false;
    this.onStatusChange("closed");
  }
}

使用示例(vue2)

import { CustomSocket } from './CustomSocket'; // 假设你的类文件名为 CustomSocket.js

initSocket() {
  this.loading = true;
  const host = `${this...}/host/${this.userInfo.user_id}`;

  this.socketInstance = new CustomSocket(host, {
    heartbeatInterval: 9000, // 每 9 秒发送一次心跳
    maxReconnectAttempts: 3, // 最多重连 3 次

    // 收到警报消息时的处理函数
    onMessage: this.handleMsg,

    // 开始连接前,展示 loading 状态
    onConnecting: () => {
      this.loading = true;
      this.getState = "";
    },

    // 成功建立连接
    onOpen: () => {
      console.log("Alert WebSocket connected");
      Notification.closeAll(); // 清除旧的警报提示
      this.loading = false;
      this.getState = "success";
    },

    // 断线重连提示
    onReconnect: () => {
      Notification.error({
        title: "提示",
        message: "待处理警报连接断开,正在尝试重连",
        duration: 10000,
      });
    },

    // 连接异常
    onError: (err) => {
      console.error("Alert WebSocket error", err);
      this.loading = false;
      this.getState = "error";
    },

    // 连接关闭
    onClose: () => {
      console.warn("Alert WebSocket closed");
      this.loading = false;
      this.getState = "error";
    },
  });
}

记得关闭页面时销毁

beforeDestroy() {
  if (this.socketInstance) {
    this.socketInstance.close();
  }
}

不唯有与他人告别
24 声望4 粉丝

不唯有与他人告别