- 使用 SocketTask 的方式去管理 webSocket 链接,每一条链路的生命周期都更加可控
- 可实现主动建立连接、心跳防断线机制、断线主动重连、提供主动断开的方法
一、如何使用 (uniapp Vue3)
<template>
</template>
<script setup>
import WebSocketClient from './WebSocketClient'
import { onLoad, onUnload } from '@dcloudio/uni-app'
let ws = null
onLoad(() => {
ws = new WebSocketClient({
url: '',
onOpen: () => {
},
onMessage: (data) => {
},
onClose: () => {}
})
})
function sendMsg() {
uni.request({
url: '后端api',
data: {...},
success: () => {
}
})
}
onUnload(() => {
ws.close()
})
</script>
二、封装的WebSocketClient代码
const STATUS = {
CLOSED: 'closed',
CONNECTING: 'connecting',
CONNECTED: 'connected',
RECONNECTING: 'reconnecting',
ERROR: 'error'
}
const DEFAULT_OPTIONS = {
url: '',
reconnectMaxTimes: 200,
reconnectTimeout: 3000,
pingTimeout: 30000,
pongTimeout: 5000,
debug: location.href.includes('debug'),
onOpen: () => {},
onMessage: () => {},
onClose: () => {},
onError: () => {},
onStatusChange: () => {}
}
class WebSocketClient {
static instance = null
constructor(options = {}) {
if (WebSocketClient.instance) return WebSocketClient.instance
this.options = { ...DEFAULT_OPTIONS, ...options }
this.socket = null
this.reconnectTimes = 0
this.isNormalClosed = false
this.heartBeatTimer = null
this.pongTimeoutTimer = null
this.reconnectTimer = null
this.isReconnecting = false
this.messageQueue = []
this.status = STATUS.CLOSED
if (!this.options.url) {
console.error('WebSocket URL 不能为空')
return
}
this.initWebSocket()
this.addEventListeners()
WebSocketClient.instance = this
}
initWebSocket() {
if (this.isConnected()) return
this.updateStatus(STATUS.CONNECTING)
this.log(`准备连接到 ${this.options.url}`)
this.socket = uni.connectSocket({
url: this.options.url,
success: () => {
uni.hideLoading()
}
})
this.socket.onOpen(() => this.handleOpen())
this.socket.onMessage((event) => this.handleMessage(event))
this.socket.onError((event) => this.handleError(event))
this.socket.onClose((event) => this.handleClose(event))
}
handleOpen() {
this.log('连接成功!')
this.updateStatus(STATUS.CONNECTED)
this.reconnectTimes = 0
this.isNormalClosed = false
this.options.onOpen()
while (this.messageQueue.length > 0) {
this.send(this.messageQueue.shift())
}
this.startHeartBeat()
}
handleMessage(event) {
try {
const data = JSON.parse(event.data)
this.options.onMessage(data)
if (data.type === 'ping') {
this.send({ type: 'pong' })
}
if (data.type === 'pong') {
clearTimeout(this.pongTimeoutTimer)
}
} catch (error) {
this.log('消息解析错误', error)
}
}
handleClose(event) {
this.updateStatus(STATUS.CLOSED)
this.isReconnecting = false
this.closeHeartBeat()
this.log('WebSocket 连接关闭', event)
if (!this.isNormalClosed) {
this.reconnect()
}
this.options.onClose(event)
}
handleError(event) {
this.updateStatus(STATUS.ERROR)
this.isReconnecting = false
this.closeHeartBeat()
this.log('WebSocket 连接错误', event)
this.reconnect()
this.options.onError(event)
}
addEventListeners() {
document.addEventListener('visibilitychange', () => {
if (!document.hidden && this.socket.readyState !== WebSocket.OPEN) {
console.warn('🔄 页面恢复可见,尝试重新连接...')
this.reconnect()
}
})
window.addEventListener('online', () => {
console.warn('🌍 网络恢复,尝试重新连接...')
if (this.socket.readyState !== WebSocket.OPEN) {
this.reconnect()
}
})
}
updateStatus(newStatus) {
this.status = newStatus
this.log(`状态更新:${newStatus}`)
this.options.onStatusChange(newStatus)
}
send(data) {
if (this.isConnected()) {
this.socket.send({ data: JSON.stringify(data) })
} else {
this.log('WebSocket 未连接,消息加入队列')
this.messageQueue.push(data)
}
}
close() {
this.isNormalClosed = true
this.updateStatus(STATUS.CLOSED)
this.closeHeartBeat()
this.messageQueue = []
clearTimeout(this.pongTimeoutTimer)
clearTimeout(this.reconnectTimer)
if (this.socket) {
this.socket.close()
this.socket = null
}
}
reconnect() {
if (this.isConnected()) {
this.updateStatus(STATUS.CONNECTED)
this.log('⚠️websocket状态正常,无需重新连接!')
return
}
if (this.isReconnecting || this.reconnectTimer) {
this.log('⚠️正在重连中,跳过本次调用')
return
}
if (this.reconnectTimes >= this.options.reconnectMaxTimes) {
this.log('⚠️达到最大重连次数,停止重连')
return
}
this.isReconnecting = true
this.reconnectTimes++
this.updateStatus(STATUS.RECONNECTING)
this.log(`第 ${this.reconnectTimes} 次尝试重连...`)
this.reconnectTimer = setTimeout(() => {
this.isReconnecting = false
this.reconnectTimer = null
this.initWebSocket()
}, this.options.reconnectTimeout)
}
startHeartBeat() {
this.closeHeartBeat()
this.heartBeatTimer = setInterval(() => {
this.send({ type: 'ping' })
this.log('心跳检测发送 ping')
this.pongTimeoutTimer = setTimeout(() => {
console.warn('🚨 未收到服务器 pong,可能断线,尝试重连...')
this.reconnect()
}, this.options.pongTimeout)
}, this.options.pingTimeout)
}
closeHeartBeat() {
clearInterval(this.heartBeatTimer)
clearTimeout(this.pongTimeoutTimer)
this.heartBeatTimer = null
}
isConnected() {
return this.socket && this.socket.readyState === WebSocket.OPEN
}
log(...args) {
if (this.options.debug) {
console.log('[WebSocket]:', ...args)
}
}
}
export default WebSocketClient