|
|
|
@ -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; |