uniapp websocket封装

32 阅读1分钟

封装 uniapp 中的 websocket

封装

utils\websocket.js

import dayjs from "dayjs";

// 心跳间隔、重连websocket间隔
const interval = 1000 * 20;

// 重连最大次数
const MAX_RECONNECT_ATTEMPTS = 1000;

export default class WS {
  constructor(options) {
    // 配置
    this.options = options;
    // WS实例
    this.socketTask = null;

    // 正常关闭
    this.normalCloseFlag = false;
    // 重新连接次数
    this.reconnectTime = 1;
    // 重新连接Timer
    this.reconnectTimer = null;
    // 心跳Timer
    this.heartTimer = null;

    // 发起连接
    this.initWS();

    // 关闭WS
    this.close = () => {
      // 正常关闭状态
      this.normalCloseFlag = true;
      // 关闭websocket
      this.socketTask.close();
      // 关闭心跳定时器
      clearInterval(this.heartTimer);
      // 关闭重连定时器
      clearTimeout(this.reconnectTimer);

      console.log("主动关闭websocket");
    };
  }

  initWS() {
    // this.options.data 连接websocket所需参数
    // const url = 'wss://后端url' + this.options.data.orgCode
    const url = this.options.url;
    console.log("发起连接:", url);

    this.socketTask = uni.connectSocket({
      url,
      success: () => {},
      fail: () => {
        console.error(`WebSocket 连接失败 ${dayjs().format("YYYY-MM-DD HH:mm:ss")}`);
        this.onDisconnected({ reason: "connect_failed" });
      },
    });
    // 监听WS
    this.watchWS();
  }

  watchWS() {
    // 监听 WebSocket 连接打开事件
    this.socketTask.onOpen(() => {
      console.log("websocket 连接成功!", dayjs().format("YYYY-MM-DD HH:mm:ss"));
      // 连接成功
      this.options.onConnected();
      // 重置连接次数
      this.reconnectTime = 1;
      // 发送心跳
      this.onHeartBeat();
      // 监听消息
      this.onMessage();
      // 关闭Toast
      uni.hideLoading();
    });

    // 监听websocket 错误
    this.socketTask.onError(() => {
      console.log("监听websocket 错误!", dayjs().format("YYYY-MM-DD HH:mm:ss"));
      // 关闭并重连
      this.socketTask.close();
    });

    // 监听 WebSocket 连接关闭事件
    this.socketTask.onClose(res => {
      console.log("监听 WebSocket 连接关闭事件!", dayjs().format("YYYY-MM-DD HH:mm:ss"));

      // 连接错误,发起重连接
      if (!this.normalCloseFlag) {
        this.onDisconnected(res);
      }
    });
  }

  // 监听消息
  onMessage() {
    // 监听websocket 收到消息
    this.socketTask.onMessage(res => {
      //收到消息
      if (res.data) {
        // this.options.onMessage(JSON.parse(res.data))
        this.options.onMessage(res.data);
      } else {
        console.log("未监听到消息:原因:", JSON.stringify(res));
      }
    });
  }

  // 断开连接
  onDisconnected(res) {
    console.log("websocket断开连接,原因:", JSON.stringify(res));
    console.log("websocket断开连接时间:", dayjs().format("YYYY-MM-DD HH:mm:ss"));
    // 关闭心跳
    clearInterval(this.heartTimer);
    // 全局Toast提示,防止用户继续发送
    // uni.showLoading({
    // 	title: 'websocket连接中…'
    // })
    // 尝试重新连接
    this.onReconnect();
  }

  // 断线重连
  onReconnect() {
    clearTimeout(this.reconnectTimer);
    if (this.reconnectTime < MAX_RECONNECT_ATTEMPTS) {
      // 指数退避 + 随机抖动
      // Math.pow(2, this.reconnectTime):使用 指数退避算法 来增加每次重试之间的间隔时间。例如,第一次重试是 2^1 = 2 倍的基础间隔,第二次是 2^2 = 4 倍,以此类推。
      // 在 WebSocket 断线重连逻辑中,随机抖动(Jitter) 是一种优化手段,用于在指数退避算法的基础上添加一个随机延迟偏移量,进一步避免多个客户端在同一时间点发起重连请求,从而减轻服务器压力。
      // 当重连间隔是固定的 interval,容易导致多个客户端同时重连,形成“惊群效应”。
      const backoff = Math.min(interval * Math.pow(2, this.reconnectTime), 40000); // 最大延迟 40s
      const jitter = Math.random() * 1000; // 添加 0~1s 的随机延迟
      const delay = backoff + jitter;

      this.reconnectTimer = setTimeout(() => {
        console.log(
          `第【${this.reconnectTime}】次重新连接中……,重连时间:${dayjs().format(
            "YYYY-MM-DD HH:mm:ss"
          )}`
        );
        this.initWS();
        this.reconnectTime++;
      }, delay);
    } else {
      uni.showModal({
        title: "温馨提示",
        content: "服务器开小差啦~",
        showCancel: false,
        confirmText: "我知道了",
        success: () => {
          uni.navigateBack();
        },
      });
    }
  }

  /** @心跳 **/
  onHeartBeat() {
    let deviceCode = uni.getStorageSync("deviceCode") || "";
    this.heartTimer = setInterval(() => {
      const heartbeatMessage = {
        type: "heartbeat",
        timestamp: Date.now(),
        deviceCode: deviceCode,
        random: (Math.random() * 1000).toFixed(0),
      };

      this.socketTask.send({
        data: JSON.stringify(heartbeatMessage),
        success: () => {
          console.log("心跳发送成功!");
        },
        fail: err => {
          console.error(
            `心跳发送失败时间:${dayjs().format("YYYY-MM-DD HH:mm:ss")},失败原因:${err}`
          );
          // 可选操作:触发重连
        },
      });
    }, interval);
  }
}

使用

import WS from '@/utils/websocket.js'  // 引用

// 使用
this.ws = new WS({
  url: `${wsUrl}/handheld/ws/home/${deviceCode}?orgCode=${orgCode}`,
  onConnected: () => {
    // 首次连接成功之后,断线重新连接后也会触发(防止断线期间对方发送消息未接收到)
  },
  onMessage: data => {
    // 监听接收到服务器消息
    console.log('data', JSON.parse(data));
  },
});


// 页面销毁,断开websocket 
 onUnload(() => { 
   // 主动关闭websocket 
   ws.close()
 })

传送门:uniapp websocket封装(建立连接、断线重连、心跳机制、主动关闭)