Newer
Older
urbanLifeline_YanAn / src / utils / ws / useWebSocket.js
@zhangzhihui zhangzhihui 9 days ago 5 KB WS + 模型
import { ref } from 'vue';

const DEFAULT_OPTIONS = {
  url: '', // websocket url
  heartBeatData: '', // 你的心跳数据
  heartBeatStartInterval: 60 * 1000, // 心跳间隔,单位ms
  heartBeatEndInterval: 5000, // 心跳间隔,单位ms  判断心跳是否成功时间
  reconnectInterval: 5000, // 断线重连间隔,单位ms
  maxReconnectAttempts: 10, // 最大重连次数
};

export const SocketStatus = {
  Connecting: '正在连接...', //表示正在连接,这是初始状态。
  Connected: '连接已建立', //表示连接已经建立。
  Disconnecting: '连接正在关闭', //表示连接正在关闭。
  Disconnected: '连接已断开', //表示连接已经关闭
};

const SocketCloseCode = 1000;

export default function useWebSocket(options = {}) {
  const state = {
    options: { ...DEFAULT_OPTIONS, ...options },

    socket: null,
    reconnectAttempts: 0, // 尝试重连次数
    reconnectTimeout: null,

    heartBetaSendTimer: null, // 心跳发送定时器
    heartBetaTimeoutTimer: null, // 心跳超时定时器

    // 心跳检测是否通过
    isHeartingSuccess: true,
  };

  // 连接状态
  const status = ref(SocketStatus.Disconnected);
  const message = ref(null);
  const error = ref(null);

  // 连接
  const connect = () => {
    disconnect();

    status.value = SocketStatus.Connecting;
    if (!window.navigator.onLine) {
      setTimeout(() => {
        status.value = SocketStatus.Disconnected;
      }, 500);
      return;
    }

    state.socket = new WebSocket(state.options.url, state.options.token);

    state.socket.onopen = openEvent => {
      console.log('socket连接:', openEvent);
      state.reconnectAttempts = 0;
      status.value = SocketStatus.Connected;
      error.value = null;
      startHeartBeat();
    };

    state.socket.onmessage = msgEvent => {
      // console.log('socket消息:', msgEvent, state.isHeartingSuccess);

      // 收到任何数据,重新开始心跳
      // startHeartBeat()

      const { data } = msgEvent;
      if (!state.isHeartingSuccess) {
        if (data === 'pong') {
          // console.log('🚀 ~ connect ~ msg1:', data);
          state.isHeartingSuccess = true;
          return;
        }
      }
      const msg = JSON.parse(data);
      // console.log('🚀 ~ connect ~ msg2:', msg);

      //心跳数据, 可自行修改
      // if (+msg.msg_id === 0) {
      //     return
      // }
      //   if (data === 'pong') return;
      message.value = msg;
    };

    state.socket.onclose = closeEvent => {
      console.log('socket关闭:', closeEvent);
      status.value = SocketStatus.Disconnected;
      // 非正常关闭,尝试重连
      if (closeEvent.code !== SocketCloseCode) {
        reconnect();
      }
    };

    state.socket.onerror = errEvent => {
      console.log('socket报错:', errEvent);
      status.value = SocketStatus.Disconnected;
      error.value = errEvent;
      // 连接失败,尝试重连
      reconnect();
    };
  };

  const disconnect = () => {
    if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) {
      console.log('socket断开连接');
      status.value = SocketStatus.Disconnecting;
      state.socket.onmessage = null;
      state.socket.onerror = null;
      state.socket.onclose = null;
      // 发送关闭帧给服务端
      state.socket.close(SocketCloseCode, 'normal closure');
      status.value = SocketStatus.Disconnected;
      state.socket = null;
    }
    stopHeartBeat();
    stopReconnect();
  };

  const startHeartBeat = () => {
    stopHeartBeat();
    onHeartBeat(() => {
      if (status.value === SocketStatus.Connected) {
        state.isHeartingSuccess = false;
        state.socket.send(state.options.heartBeatData);
        console.log('socket心跳发送:', state.options.heartBeatData);
      }
    });
  };

  const onHeartBeat = callback => {
    state.heartBetaSendTimer = setTimeout(() => {
      callback && callback();
      state.heartBetaTimeoutTimer = setTimeout(() => {
        // 心跳超时,直接关闭socket,抛出自定义code=4444, onclose里进行重连
        // state.socket.close(4444, 'heart timeout');

        if (!state.isHeartingSuccess) {
          state.socket.close(4444, 'heart timeout');
          state.isHeartingSuccess = true;
        } else {
          console.log('心跳检测成功,开始新一轮的心跳检测');
          startHeartBeat();
        }
      }, state.options.heartBeatEndInterval);
    }, state.options.heartBeatStartInterval);
  };

  const stopHeartBeat = () => {
    state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer);
    state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer);
  };

  // 重连
  const reconnect = () => {
    if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) {
      return;
    }
    stopHeartBeat();
    if (state.reconnectAttempts < state.options.maxReconnectAttempts) {
      console.log('socket重连:', state.reconnectAttempts);

      // 重连间隔,5秒起步,下次递增1秒
      const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000);
      console.log('间隔时间:', interval);
      state.reconnectTimeout = setTimeout(() => {
        if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) {
          connect();
        }
      }, interval);
      state.reconnectAttempts += 1;
    } else {
      status.value = SocketStatus.Disconnected;
      stopReconnect();
    }
  };

  // 停止重连
  const stopReconnect = () => {
    state.reconnectTimeout && clearTimeout(state.reconnectTimeout);
  };

  return {
    status,
    message,
    error,
    connect,
    disconnect,
  };
}