封装 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()
})