uniapp 多 WebSocket 管理方案:支持断线重连、心跳检测、自动复用

内容分享16小时前发布
0 0 0

当你的项目只有一个 WebSocket,写法可以随便。
当你的项目需要管理多个 WebSocket,你会发现页面切换、状态同步、回调解绑,全都变麻烦。

这篇文章带来一套通用、可扩展、多实例复用的 WebSocket 管理架构,适用于:

在线问诊系统多订单实时同步实时客服聊天IoT 设备状态监听

使用方式非常优雅,只需三行代码即可建立稳定连接👇:


let socketManager = null
const socketStore = useSocketStore()

socketManager = socketStore.get(websocketId.value, handleSocketMsg)
socketManager.updateMessageHandler(handleSocketMsg)
socketManager.connect()

目录

🔥 背景与问题🧩 架构设计📍 useSocketStore 代码解析📍 useSocketManager 代码解析🔁 心跳与重连机制时序图🚀 使用示例⚠️ 踩坑记录🧾 总结

🔥 背景与问题

问题 表现
重复创建连接 多次进入页面就生成多个 socket
回调 handler 堆积 退出页面再进,消息触发多次
假在线状态 安卓后台休眠后 WebSocket 实际断开,但看起来正常
多个业务模块使用 WebSocket 难管理 IM、订单消息、状态推送 → 多实例需要隔离管理

要解决这些问题,需要:

连接复用心跳机制断线重连动态回调绑定自动清理机制

🧩 架构设计


 ┌──────────────────────┐
 │ Pinia store          │
 │ 缓存所有 socket 实例 │
 │ 复用 / 移除 / 查询   │
 └───────────▲──────────┘
             │
             ▼
 ┌──────────────────────┐
 │ useSocketManager     │
 │ 单实例逻辑:         │
 │ 心跳/发送/监听/重连  │
 └───────────▲──────────┘
             │
             ▼
       应用业务页面

目标是让业务层保持干净,像 axios 调用一样。

📍 useSocketStore 代码解析

用于维护多个 socket 实例,并防止重复创建。


import { defineStore } from 'pinia'
import { useSocketManager } from '@/utils/useSocketManager.js'

export const useSocketStore = defineStore('socketStore', {
  state: () => ({
    sockets: new Map(),
    websocketOrderMap: new Map()
  }),

  actions: {
    get(orderId, onMessage) {
      if (!orderId) return null

      if (this.sockets.has(orderId)) {
        const socket = this.sockets.get(orderId)
        const status = socket?.getConnectionStatus?.()
        if (status?.isConnected) return socket
      }

      const socket = useSocketManager(orderId, onMessage, this)
      this.sockets.set(orderId, socket)
      return socket
    },

    remove(orderId) {
      if (!this.sockets.has(orderId)) return
      const socket = this.sockets.get(orderId)
      socket?.close(false)
      this.sockets.delete(orderId)
    },

    clearAll() {
      this.sockets.forEach(socket => socket?.close(false))
      this.sockets.clear()
      this.websocketOrderMap.clear()
    }
  }
})

📍 useSocketManager 代码解析

核心能力:

心跳重连机制(指数退避)防重复连接动态 message handler 替换(避免回调叠加)


import { ref } from 'vue'

const connectionInstances = new Map()

export function useSocketManager(orderId, onMessage, socketStore) {
  const socketTask = ref(null)
  const isConnected = ref(false)
  const heartbeatTimer = ref(null)
  const reconnectTimer = ref(null)
  const reconnectCount = ref(0)
  const MAX_RECONNECT = 5
  let onMessageHandler = onMessage

  const baseUrl = import.meta.env.VITE_APP_WS_HLW_API
  const wsUrl = `${baseUrl}/${orderId}_flfmyl_prescription`
🔗 connect()

const connect = () => {
  if (connectionInstances.has(orderId)) {
    const exist = connectionInstances.get(orderId)
    if (exist.isConnected) return
  }

  close(false)

  socketTask.value = uni.connectSocket({
    url: wsUrl
  })

  socketTask.value.onOpen(() => {
    isConnected.value = true
    reconnectCount.value = 0
    startHeartbeat()
  })

  socketTask.value.onMessage((res) => {
    let data = {}
    try { data = JSON.parse(res.data) } catch {}
    onMessageHandler(data)
  })

  socketTask.value.onError(() => tryReconnect())
  socketTask.value.onClose(() => tryReconnect())

  connectionInstances.set(orderId, socketTask.value)
}
🔁 重连(指数退避)

const tryReconnect = () => {
  if (reconnectCount.value >= MAX_RECONNECT) return
  reconnectCount.value++
  const delay = Math.min(1000 * Math.pow(2, reconnectCount.value), 30000)
  reconnectTimer.value = setTimeout(() => connect(), delay)
}
❤️ 心跳机制

const startHeartbeat = () => {
  stopHeartbeat()
  heartbeatTimer.value = setInterval(() => {
    if (isConnected.value) send({ type: 'ping' })
  }, 15000)
}
🧹 关闭连接

const close = (removeFromStore = true) => {
  stopHeartbeat()
  clearTimeout(reconnectTimer.value)
  connectionInstances.delete(orderId)

  if (socketTask.value) {
    socketTask.value.close({})
    socketTask.value = null
  }

  if (removeFromStore && socketStore) {
    socketStore.remove(orderId)
  }

  isConnected.value = false
}
🎯 动态替换回调

const updateMessageHandler = (fn) => {
  onMessageHandler = fn
}

🔁 心跳与重连机制时序图


connect
  │
  ├──> onOpen → startHeartbeat → 可通信
  │
  ├──> onError / onClose → tryReconnect (指数递增)
  │
  └──> 达到上限 → 停止重连

🚀 使用示例


const socketStore = useSocketStore()

const handleMsg = (msg) => {
  console.log("收到:", msg)
}

const initSocket = () => {
  const socket = socketStore.get(orderId, handleMsg)
  socket.updateMessageHandler(handleMsg)
  socket.connect()
}

发送消息:


socketManager.send({ text: '你好医生' })

关闭:


socketManager.close()

⚠️ 踩坑记录

坑点 解决方式
进入页面多次绑定 onMessage → 回调叠加 使用
updateMessageHandler
后台断开但状态假在线 心跳机制检测
多 socket 混乱 使用 Pinia Map 分发实例

🧾 总结

这套方案具备:

多 WebSocket 实例管理自动复用机制健壮的断线重连策略心跳检测-动态消息 handler 替换非常适用于需要实时消息、安全稳定通信的场景。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...