From bbe4c2a637623c4d33f9cf28f050581b21886ed4 Mon Sep 17 00:00:00 2001 From: liaoxinyu Date: Fri, 15 May 2026 11:36:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9ws?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/server/Socket.js | 344 +++++++++++++++++++++++++------- src/views/contract/handicap.vue | 3 + 2 files changed, 274 insertions(+), 73 deletions(-) diff --git a/src/api/server/Socket.js b/src/api/server/Socket.js index 3e3cdea..2d82662 100644 --- a/src/api/server/Socket.js +++ b/src/api/server/Socket.js @@ -1,52 +1,216 @@ +// class Socket { + +// constructor(link, ...args) { +// // 初始化socket +// if (link.constructor === WebSocket) { +// this.socket = link; +// } else { +// this.socket = new WebSocket(link); +// } + +// // this.socket.binaryType = 'arraybuffer'; + +// this.doOpen(); + +// // 连接状态的标识符 +// this.readyState = this.socket.readyState; +// // 订阅/发布模型 +// this._events = { +// // 订阅的事件 : 发布的方法 + +// }; + +// // 定时验证的标识符 +// this.heartBeatTimer = null; + +// } + +// // 执行socket并发布事件 +// doOpen() { + +// this.afterOpenEmit = []; + +// // 执行socket连接 并初始化验证请求 +// this.socket.addEventListener("open", evt => this.onOpen(evt)); + +// // 接收socket数据 +// this.socket.addEventListener("message", evt => this.onMessage(evt)); + +// // 关闭socket连接 +// this.socket.addEventListener("close", evt => this.onClose(evt)); + +// // 请求发生错误 +// this.socket.addEventListener("error", err => this.onError(err)); + +// } + +// // 发布后通知订阅者 +// Notify(entry) { +// // 检查是否有订阅者 返回队列 +// const cbQueue = this._events[entry.Event]; +// if (cbQueue && cbQueue.length) { +// for (let callback of cbQueue) { +// if (callback instanceof Function) callback(entry.Data); +// } +// } +// } + +// // 请求数据的方法 +// onOpen(evt) { + +// // 每隔20s检查连接 +// // this.heartBeatTimer = setInterval(() => this.send({ +// // 'cmd': 'ping', +// // 'args': '' +// // }), 20000); + +// // 通知订阅 +// this.Notify({Event: 'open', Data : evt}); +// } + +// /** +// * 订阅所有的数据 +// * @param {array|object} datas 订阅参数集合 +// */ +// send(datas) { +// if (datas.constructor != Array) { +// datas = [datas]; +// } + +// for (let item of datas) { +// this.socket.send(JSON.stringify(item)); +// } +// } + + +// onMessage(evt) { + +// try { + +// // 解析推送的数据 +// const data = JSON.parse(evt.data); + +// // 通知订阅者 +// this.Notify({ +// Event: 'message', +// Data: data +// }); + +// } catch (err) { +// console.error(' >> Data parsing error:', err); + +// // 通知订阅者 +// this.Notify({ +// Event: 'error', +// Data: err +// }); +// } +// } + +// // 添加事件监听 +// on(name, handler) { +// this.subscribe(name, handler); +// } + +// // 取消订阅事件 +// off(name, handler) { +// this.unsubscribe(name, handler); +// } + +// // 订阅事件的方法 +// subscribe(name, handler) { +// if (this._events.hasOwnProperty(name)) { +// this._events[name].push(handler); // 追加事件 +// } else { +// this._events[name] = [handler]; // 添加事件 +// } +// } + +// // 取消订阅事件 +// unsubscribe(name, handler) { + +// let start = this._events[name].findIndex(item => item === handler); + +// // 删除该事件 +// this._events[name].splice(start, 1); + +// } + +// checkOpen() { +// return this.readyState >= 2; +// } + +// onClose(evt) { +// this.Notify({Event: 'close', Data : evt}); +// } + + +// onError(err) { +// this.Notify({Event: 'error', Data : err}); +// } + +// emit(data) { +// return new Promise((resolve) => { +// this.send(JSON.stringify(data)); +// this.on('message', function (data) { +// resolve(data); +// }); +// }); +// } + +// doClose() { +// this.socket.close(); +// } + +// destroy() { +// if (this.heartBeatTimer) { +// clearInterval(this.heartBeatTimer); +// this.heartBeatTimer = null; +// } +// this.doClose(); +// this._events = {}; +// this.readyState = 0; +// this.socket = null; +// } +// } + +// export default Socket + + class Socket { constructor(link, ...args) { - // 初始化socket + // ✅ 修复1:确保 this.link 存的是字符串,方便后面断线重连 if (link.constructor === WebSocket) { this.socket = link; + this.link = link.url; } else { this.socket = new WebSocket(link); + this.link = link; } - // this.socket.binaryType = 'arraybuffer'; - this.doOpen(); - // 连接状态的标识符 this.readyState = this.socket.readyState; - // 订阅/发布模型 - this._events = { - // 订阅的事件 : 发布的方法 - - }; - - // 定时验证的标识符 + this._events = {}; + this.heartBeatTimer = null; - + + // ✅ 新增:重连相关的标识符 + this.reconnectTimer = null; + this.isReconnecting = false; + this.manualClose = false; // 判断是否是用户主动退出页面 } - // 执行socket并发布事件 doOpen() { - this.afterOpenEmit = []; - - // 执行socket连接 并初始化验证请求 this.socket.addEventListener("open", evt => this.onOpen(evt)); - - // 接收socket数据 this.socket.addEventListener("message", evt => this.onMessage(evt)); - - // 关闭socket连接 this.socket.addEventListener("close", evt => this.onClose(evt)); - - // 请求发生错误 this.socket.addEventListener("error", err => this.onError(err)); - } - // 发布后通知订阅者 Notify(entry) { - // 检查是否有订阅者 返回队列 const cbQueue = this._events[entry.Event]; if (cbQueue && cbQueue.length) { for (let callback of cbQueue) { @@ -55,118 +219,152 @@ class Socket { } } - // 请求数据的方法 onOpen(evt) { + // 连接成功,重置状态 + this.isReconnecting = false; + this.manualClose = false; + + // 每次连上先清空旧的心跳,防止重复 + if (this.heartBeatTimer) clearInterval(this.heartBeatTimer); + + // 每隔20s发送心跳 + this.heartBeatTimer = setInterval(() => { + this.send({ + 'cmd': 'ping', + 'args': '' + }); + }, 20000); - // 每隔20s检查连接 - // this.heartBeatTimer = setInterval(() => this.send({ - // 'cmd': 'ping', - // 'args': '' - // }), 20000); - - // 通知订阅 this.Notify({Event: 'open', Data : evt}); } - /** - * 订阅所有的数据 - * @param {array|object} datas 订阅参数集合 - */ send(datas) { if (datas.constructor != Array) { - datas = [datas]; + datas = [datas]; } for (let item of datas) { - this.socket.send(JSON.stringify(item)); + // ✅ 修复2:发送前必须检查状态,彻底解决 "CLOSING or CLOSED state" 报错! + if (this.socket && this.socket.readyState === 1) { + this.socket.send(JSON.stringify(item)); + } else { + console.warn("WebSocket未连接,已拦截发送请求"); + } } } - onMessage(evt) { - try { - - // 解析推送的数据 const data = JSON.parse(evt.data); - - // 通知订阅者 - this.Notify({ - Event: 'message', - Data: data - }); - + this.Notify({ Event: 'message', Data: data }); } catch (err) { console.error(' >> Data parsing error:', err); - - // 通知订阅者 - this.Notify({ - Event: 'error', - Data: err - }); + this.Notify({ Event: 'error', Data: err }); } } - // 添加事件监听 on(name, handler) { this.subscribe(name, handler); } - // 取消订阅事件 off(name, handler) { this.unsubscribe(name, handler); } - // 订阅事件的方法 subscribe(name, handler) { if (this._events.hasOwnProperty(name)) { - this._events[name].push(handler); // 追加事件 + this._events[name].push(handler); } else { - this._events[name] = [handler]; // 添加事件 + this._events[name] = [handler]; } } - // 取消订阅事件 unsubscribe(name, handler) { - + if (!this._events[name]) return; let start = this._events[name].findIndex(item => item === handler); - - // 删除该事件 - this._events[name].splice(start, 1); - + if (start > -1) { + this._events[name].splice(start, 1); + } } checkOpen() { return this.readyState >= 2; } + // ✅ 修复3:核心重连逻辑 + reconnect() { + // 如果是手动关闭(退出页面),或者正在重连中,则放弃重连 + if (this.manualClose || this.isReconnecting) return; + + this.isReconnecting = true; + + // 断网了,立刻停止发心跳 + if (this.heartBeatTimer) { + clearInterval(this.heartBeatTimer); + this.heartBeatTimer = null; + } + + console.log("WebSocket 掉线,3秒后尝试重新连接..."); + + this.reconnectTimer = setTimeout(() => { + try { + this.socket = new WebSocket(this.link); + this.doOpen(); + } catch (e) { + console.error("重连失败", e); + // 即使抛出异常也解除锁,等待下一次重连尝试 + this.isReconnecting = false; + this.reconnect(); + return; + } + }, 3000); + } + onClose(evt) { this.Notify({Event: 'close', Data : evt}); + // 触发关闭时,只要不是主动退出的,就去重连 + if (!this.manualClose) this.reconnect(); } - onError(err) { this.Notify({Event: 'error', Data : err}); + // 发生错误断开时,去重连 + if (!this.manualClose) this.reconnect(); } + // ✅ 修复4:解决内存泄漏,用完立刻注销 emit(data) { return new Promise((resolve) => { this.send(JSON.stringify(data)); - this.on('message', function (data) { - resolve(data); - }); + + const handler = (resData) => { + resolve(resData); + this.off('message', handler); // 立刻注销,释放内存 + }; + + this.on('message', handler); }); } doClose() { - this.socket.close(); + this.manualClose = true; // 标记为主动关闭,防止变成僵尸无限重连 + if (this.socket) { + this.socket.close(); + } } destroy() { + this.manualClose = true; + if (this.heartBeatTimer) { clearInterval(this.heartBeatTimer); this.heartBeatTimer = null; } + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + this.doClose(); this._events = {}; this.readyState = 0; @@ -174,4 +372,4 @@ class Socket { } } -export default Socket +export default Socket; \ No newline at end of file diff --git a/src/views/contract/handicap.vue b/src/views/contract/handicap.vue index 6d2d423..91d7431 100644 --- a/src/views/contract/handicap.vue +++ b/src/views/contract/handicap.vue @@ -213,6 +213,9 @@ export default { // this.$emit("input", this.newPriceObj); }); }, + beforeDestroy() { + this.unLink(this.symbol); + }, // 获取盘口 methods: { parseTime: date.parseTime,