Skip to content

uni-app的WebSocket

uni-app的WebSocket封装类,支持心跳检测、心跳超时重连、断线重连等。uni.connectSocket

TypeScript封装类

ts
/**
 * WebSocket 封装类
 * - 支持心跳检测
 * - 支持心跳超时延时重连
 * - 支持断线延时重连
 */
export class SuWebSocket {
  // WebSocket 地址
  private readonly url: string
  // WebSocket 任务
  private socketTask: any = null
  // 心跳检测定时器
  private heartBeatTimer: any = null
  // 重连定时器
  private reconnectTimer: any = null
  // 心跳超时检测定时器
  private heartBeatTimeoutTimer: any = null
  // 重连间隔(毫秒)
  private reconnectDelay: number = 5000
  // 心跳检测间隔(毫秒)
  private heartBeatInterval: number = 30000
  // 心跳检测超时(毫秒)
  private heartBeatTimeout: number = 10000
  // 是否处于连接状态
  private isConnected: boolean = false
  // 是否处于连接中
  private isConnecting: boolean = false
  // 重连次数
  private reconnectAttempts: number = 0
  // 是否允许重连
  private allowReconnect: boolean = true
  // 回调函数:连接成功
  private onOpenCallback: (() => void) | null = null
  // 回调函数:接收到消息
  private onMessageCallback: ((data: any) => void) | null = null
  // 回调函数:连接关闭
  private onCloseCallback: (() => void) | null = null
  // 回调函数:连接错误
  private onErrorCallback: ((error: any) => void) | null = null

  /**
   * 构造函数
   * @param url WebSocket 地址
   */
  constructor(url: string) {
    this.url = url
  }

  /**
   * 设置连接成功回调
   */
  onOpen(callback: () => void): void {
    this.onOpenCallback = callback
  }

  /**
   * 设置消息接收回调
   */
  onMessage(callback: (data: any) => void): void {
    this.onMessageCallback = callback
  }

  /**
   * 设置连接关闭回调
   */
  onClose(callback: () => void): void {
    this.onCloseCallback = callback
  }

  /**
   * 设置错误回调
   */
  onError(callback: (error: any) => void): void {
    this.onErrorCallback = callback
  }

  /**
   * 设置是否允许重连
   * @param allow 是否允许重连
   */
  setAllowReconnect(allow: boolean): void {
    this.allowReconnect = allow;
  }

  /**
   * 建立 WebSocket 连接
   */
  connect(): void {
    if (this.isConnecting || this.isConnected) {
      return
    }

    this.allowReconnect = true;
    this.isConnecting = true

    this.socketTask = uni.connectSocket({
      url: this.url,
      success: () => {
        console.log('WebSocket 连接成功')
      },
      fail: (err) => {
        console.error('WebSocket 连接失败', err)
        this.handleReconnect()
      }
    })

    this.bindSocketEvents()
  }

  /**
   * 绑定 WebSocket 事件
   */
  private bindSocketEvents(): void {
    // 监听连接打开
    this.socketTask.onOpen(() => {
      console.log('WebSocket 连接已打开')
      this.isConnecting = false
      this.isConnected = true
      this.reconnectAttempts = 0
      this.startHeartBeat()
      if (this.onOpenCallback) {
        this.onOpenCallback()
      }
    })

    // 监听消息接收
    this.socketTask.onMessage((res: any) => {
      console.log('收到服务器消息:', res.data)
      if (res.data === 'pong') {
        this.handlePong()
        return
      }
      if (this.onMessageCallback) {
        this.onMessageCallback(res.data)
      }
    })

    // 监听连接关闭
    this.socketTask.onClose(() => {
      console.log('WebSocket 连接已关闭')
      this.isConnected = false
      this.isConnecting = false
      this.stopHeartBeat()
      if (this.onCloseCallback) {
        this.onCloseCallback()
      }
      this.handleReconnect()
    })

    // 监听连接错误
    this.socketTask.onError((err: any) => {
      console.error('WebSocket 发生错误:', err)
      this.isConnected = false
      this.isConnecting = false
      this.stopHeartBeat()
      if (this.onErrorCallback) {
        this.onErrorCallback(err)
      }
      this.handleReconnect()
    })
  }

  /**
   * 处理 pong 响应
   */
  private handlePong(): void {
    // 清除心跳超时定时器
    if (this.heartBeatTimeoutTimer) {
      clearTimeout(this.heartBeatTimeoutTimer)
      this.heartBeatTimeoutTimer = null
    }
    console.log('收到服务器 pong 响应')
  }

  /**
   * 处理心跳超时
   */
  private handleHeartBeatTimeout(): void {
    console.warn('心跳超时,未收到服务器 pong 响应')
    // 关闭当前连接并触发重连
    this.close()
    this.handleReconnect()
  }

  /**
   * 处理重连逻辑
   */
  private handleReconnect(): void {
    // 如果不允许重连,则不执行重连逻辑
    if (!this.allowReconnect) {
      return;
    }

    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
    }

    this.reconnectTimer = setTimeout(() => {
      this.reconnectAttempts++
      console.log(`WebSocket 尝试重连第 ${this.reconnectAttempts} 次`)
      this.connect()
    }, this.reconnectDelay)
  }

  /**
   * 发送消息
   */
  send(data: any): void {
    if (this.socketTask && this.isConnected) {
      this.socketTask.send({
        data: data,
        success: () => {
          console.log('消息发送成功')
        },
        fail: (err: any) => {
          console.error('消息发送失败', err)
        }
      })
    } else {
      console.warn('WebSocket 未连接,无法发送消息')
    }
  }

  /**
   * 开始心跳检测
   */
  private startHeartBeat(): void {
    this.stopHeartBeat()
    this.heartBeatTimer = setInterval(() => {
      this.sendPing()
    }, this.heartBeatInterval)
  }

  /**
   * 发送 ping 心跳包
   */
  private sendPing(): void {
    // 发送 ping
    this.send('ping')

    // 启动心跳超时检测
    if (this.heartBeatTimeoutTimer) {
      clearTimeout(this.heartBeatTimeoutTimer)
    }

    this.heartBeatTimeoutTimer = setTimeout(() => {
      this.handleHeartBeatTimeout()
    }, this.heartBeatTimeout)
  }

  /**
   * 停止心跳检测
   */
  private stopHeartBeat(): void {
    if (this.heartBeatTimer) {
      clearInterval(this.heartBeatTimer)
      this.heartBeatTimer = null
    }

    // 清除心跳超时定时器
    if (this.heartBeatTimeoutTimer) {
      clearTimeout(this.heartBeatTimeoutTimer)
      this.heartBeatTimeoutTimer = null
    }
  }

  /**
   * 销毁WebSocket实例
   */
  destroy(): void {
    // 设置不允许重连,防止在关闭过程中触发重连
    this.allowReconnect = false;
    this.close()
  }

  /**
   * 关闭 WebSocket 连接
   */
  close(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }

    this.stopHeartBeat()

    if (this.socketTask) {
      this.socketTask.close({
        code: 1000,
        reason: '用户主动关闭连接'
      })
      this.socketTask = null
    }

    this.isConnected = false
    this.isConnecting = false
  }

  /**
   * 获取连接状态
   */
  getConnectedStatus(): { isConnected: boolean; isConnecting: boolean } {
    return {
      isConnected: this.isConnected,
      isConnecting: this.isConnecting
    }
  }
}

/**
 * 创建 WebSocket 实例
 * @param url
 * @return SuWebSocket
 */
export function makeWebSocket(url: string): SuWebSocket {
  return new SuWebSocket(url)
}

使用

vue
<script setup lang="ts">import { onMounted, onUnmounted } from 'vue'

// 创建 WebSocket 实例
const wsManager = new SuWebSocket('wss://example.com/websocket')

// 处理接收到的消息
function handleMessage(data: string) {
  if (data === 'pong') {
    return
  }
  
  try {
    const message = JSON.parse(data)
    // 根据消息类型处理业务逻辑
    switch (message.event) {
      case 'refresh_data':
        // 更新页面数据
        updatePageData(message.data)
        break
      case 'notification':
        // 显示通知
        showNotification(message.data)
        break
    }
  } catch (error) {
    console.error('解析消息失败:', error)
  }
}

// 更新页面数据
function updatePageData(data: any) {
  // 实现你的数据更新逻辑
  console.log('更新数据:', data)
}

// 显示通知
function showNotification(data: any) {
  // 实现你的通知显示逻辑
  console.log('显示通知:', data)
}

onMounted(() => {
  // 设置回调
  wsManager.onOpen(() => {
    console.log('WebSocket 连接已建立')
  })
  
  wsManager.onMessage(handleMessage)
  
  wsManager.onClose(() => {
    console.log('WebSocket 连接已关闭')
  })
  
  wsManager.onError((error) => {
    console.error('WebSocket 错误:', error)
  })
  
  // 建立连接
  wsManager.connect()
})

onUnmounted(() => {
  // 组件卸载时关闭连接
  wsManager.destroy()
})
</script>
ts
// 发送文本消息
wsManager.send('hello server')

// 发送 JSON 对象
wsManager.send({
  event: 'user_action',
  data: {
    action: 'click',
    element: 'button'
  }
})