实现大小屏联动
- 安装插件
js
npm i sockjs-client -D
npm i stompjs -D
- 封装 websocket
js
import { defineStore } from "pinia";
import SockJS from "sockjs-client/dist/sockjs.min.js";
import Stomp from "stompjs";
// 心跳和重连常量配置
const HEARTBEAT_INTERVAL = 20_000; // 20秒发送一次PING
const PONG_TIMEOUT = 25_000; // 25秒内未收到PONG视为超时
const MAX_RECONNECT_ATTEMPTS = 10; // 最大重连次数
const RECONNECT_EXPONENTIAL_BACKOFF = true; // 使用指数退避算法
export const useWebSocketStore = defineStore("webSocket", {
state: () => ({
socket: null,
stompClient: null,
headerToken: {},
loginStatus: false,
currentIndex: 0,
messages: [], // 存储从 WebSocket 接收到的消息,
socketMessage: null,
// 添加一个标志来表示连接是否已建立
connectionEstablished: false,
reconnectAttempts: 0, // 重连次数
maxReconnectAttempts: 5, // 最大重连次数
lastPongTime: 0, // 上次收到PONG的时间
}),
actions: {
initSocket() {
if (this.socket && this.stompClient) {
console.warn("WebSocket already initialized.");
return;
}
// 环境配置
const isLocal = ["192.168.10.xxx", "localhost", "172.16.80.xxx"].includes(
location.hostname
);
const baseUrl = isLocal ? "https://xxxxxx" : location.origin;
//连接服务端提供的通信接口,连接以后才可以订阅广播消息和个人消息
this.socket = new SockJS(
`${baseUrl}/xxxxxxx`
);
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.over(this.socket);
// 配置心跳参数 (关键修复)
this.stompClient.heartbeat.outgoing = HEARTBEAT_INTERVAL;
this.stompClient.heartbeat.incoming = 0;
// 定义客户端的认证信息,按需求配置
this.headerToken = {
Authorization: "",
};
// 向服务器发起websocket连接
this.stompClient.connect(
this.headerToken,
(frame) => {
this.loginStatus = true;
// 更新连接状态
this.reconnectAttempts = 0; // 重置重连次数
this.connectionEstablished = true; // 连接已建立
this.startHeartbeat(); // 启动心跳
this.subscribeTopic();
},
(err) => {
// 连接发生错误时的处理函数
this.stompClient = null;
this.handleDisconnect();
// 首次连接失败直接重连
if (this.reconnectAttempts === 0) {
this.scheduleReconnect();
}
}
);
this.socket.onclose = () => {
this.handleDisconnect();
};
},
// 处理心跳响应
handlePong() {
this.lastPongTime = Date.now();
clearTimeout(this.pongTimeoutTimer);
},
// 统一重连调度
scheduleReconnect() {
this.reconnectAttempts++;
// 计算等待时间
let delay;
if (RECONNECT_EXPONENTIAL_BACKOFF) {
delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
} else {
delay = 5000; // 固定5秒重连
}
console.log(
`将在 ${delay}ms 后尝试重连 (尝试次数: ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`
);
setTimeout(() => {
this.cleanup();
this.initSocket();
}, delay);
},
// 处理断开连接
handleDisconnect() {
this.clearTimers();
// 自动重连逻辑
if (
!this.reconnectAttempts ||
this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS
) {
this.scheduleReconnect();
} else {
console.error(
`已达到最大重连次数(${MAX_RECONNECT_ATTEMPTS}),停止重连`
);
}
},
// 启动心跳检测
startHeartbeat() {
this.clearTimers();
// 记录初始PONG时间
this.lastPongTime = Date.now();
// 定时发送心跳
this.heartbeatTimer = setInterval(() => {
if (this.stompClient?.connected) {
try {
this.stompClient.send(
"/xx/xxx",
this.headerToken,
JSON.stringify({ type: "ping" })
);
// 设置PONG超时检测
this.pongTimeoutTimer = setTimeout(() => {
if (Date.now() - this.lastPongTime > PONG_TIMEOUT) {
console.warn("PONG响应超时,触发重连");
this.handleDisconnect();
}
}, PONG_TIMEOUT);
} catch (error) {
console.error("发送心跳失败:", error);
this.handleDisconnect();
}
}
}, HEARTBEAT_INTERVAL);
},
subscribeTopic() {
if (!this.stompClient) return;
this.stompClient.subscribe("/xxx/xxx", (broadcast) => {
this.socketMessage = JSON.parse(broadcast.body);
if (this.socketMessage?.tpye == "ping") {
this.handlePong();
}
});
},
cleanup() {
if (this.stompClient?.connected) {
this.stompClient.disconnect();
}
this.clearTimers();
this.stompClient = null;
this.socket = null;
},
clearTimers() {
clearInterval(this.heartbeatTimer);
clearTimeout(this.pongTimeoutTimer);
this.heartbeatTimer = null;
this.pongTimeoutTimer = null;
},
clearMessages() {
this.messages = [];
this.socketMessage = null;
},
send(method, info) {
console.log("send", this.stompClient);
try {
if (!this.stompClient || !this.loginStatus) {
// 增加对 loginStatus 的检查
console.error("WebSocket connection is not established yet.");
return;
}
let param =
info === null
? null
: typeof info === "string"
? info
: JSON.stringify(info);
this.stompClient.send(method, this.headerToken, param);
} catch (e) {
this.handleDisconnect();
return;
}
},
},
});
- vue3 组件中使用
js
import { useWebSocketStore } from '@/stores/webSocket'; // 引入WebSocket Store
const webSocketStore = useWebSocketStore(); // 实例化WebSocket Store
// 小屏发送消息
const websocketApi = ref({
setCode:'/app/xxxx'
})
let param = {a:1,b:2}
webSocketStore.send(websocketApi.value.setCode,param)
// 订阅消息,监听消息
/ 监听 messages 数组的变化
watch(
() => webSocketStore.socketMessage,
(newVal, oldVal) => {
console.log(newVal,'bignewVal');
// 处理对应的业务逻辑
},
{ immediate: true, deep: true }
)