项目级WebSocket客户端工厂类设计详解及技巧总结
1. 设计思路
WebSocket客户端工厂类是一个用于管理和控制WebSocket连接的实用工具。本文将详细介绍它的设计思路以及包含的属性和方法。
1.1 设计思路
-
参数接口和构造方法 在代码中,首先定义了一个
WebSocketParam
接口,用于配置WebSocket连接的参数,包括IP地址、消息处理函数和是否可关闭连接等。然后,在构造方法中通过参数初始化工厂类的属性,包括参数配置和相关回调函数的设置。 -
连接管理和重连机制 工厂类通过使用WebSocket实例化对象来建立连接。在连接建立时,设置一个心跳检查定时器,以定期向服务器发送心跳消息,维持连接的稳定性。如果发生连接异常,则自动进行重新连接,确保连接的可靠性。
-
消息处理函数和发送消息接口 工厂类通过设置
onmessage
回调函数来处理接收到的消息。当有消息到达时,调用该回调函数进行处理。同时,提供了一个sendMsg
接口用于向WebSocket发送消息。 -
连接的关闭和销毁 通过设置
closeCallback
函数,判断是否允许关闭WebSocket连接。如果不允许关闭,则执行重新连接操作。此外,提供了一个destroy
方法,用于关闭连接并重置相关参数,释放资源。
1.2 属性和方法
-
属性:
wsInstance
: WebSocket实例对象param
: WebSocketParam对象,用于配置WebSocket连接的参数lockReconnect
: 重连锁,防止重连方法被触发多次checkTimer
: 心跳间隔器的句柄,用于定时执行心跳检查closeable
: WebSocket连接是否可关闭的标志isFirstConnect
: 是否是首次连接的标志onmessage
: 消息处理函数
-
方法:
connectCallback()
: WebSocket连接建立时的回调函数,设置心跳检查定时器并发送不同的消息给后端errorCallback(e)
: WebSocket出现错误时的回调函数,重新连接closeCallback()
: WebSocket连接关闭时的回调函数,判断是否允许关闭连接,如不允许则重新连接connect()
: 建立WebSocket连接的方法,实例化WebSocket对象并设置回调函数reconnect()
: 重新连接WebSocket的方法,根据设定的时间间隔进行连接重试sendMsg(msg: any)
: 向WebSocket发送消息的方法destroy()
: 销毁WebSocket连接的方法,包括清除定时器和关闭连接等操作
1.3 技巧总结
- 使用参数接口:通过定义参数接口,可以更清晰地配置和管理WebSocket连接的参数,增加代码的可读性和维护性。
- 利用构造方法进行初始化:构造方法是实例化工厂类时的入口,可以在其中进行相关属性的初始化和回调函数的设置。
- 设置心跳检查定时器:通过定时器定期发送心跳消息,可以保持WebSocket连接的稳定性。
- 处理错误和关闭事件:根据实际需求,实现错误处理和关闭事件的回调函数,保证针对性的操作,如重新连接或判断是否允许关闭连接。
- 提供发送消息接口:封装一个发送消息的接口,方便外部调用者向WebSocket发送消息。
- 销毁连接时的资源释放:在销毁连接时,经常需要释放定时器和关闭连接,确保资源的正确释放和重置。
通过以上设计思路和技巧,我们实现了一个可靠的WebSocket连接和重连机制的工厂类。这个工厂类简化了WebSocket连接的管理和操作,提供了方便的接口和灵活的配置选项,使得开发人员能够更轻松地实现实时通信功能,并增加了连接的稳定性和可靠性。
2. 实现代码
ts
export interface WebSocketParam {
ip: string;
onmessage: (msg: any) => void;
closeable: boolean;
}
type TimerInsert = {
mountedTimerRelative?: (msg: any) => void;
};
// websocket客户端工厂
export default class WSClientFactory {
// websocket实例对象
wsInstance: WebSocket & TimerInsert = null;
// 构造参数
param: WebSocketParam;
// 重联锁
lockReconnect = false;
// 心跳间隔器的句柄
checkTimer: NodeJS.Timer = null;
// 表明此websocket是否是可以关闭的(如果不可以关闭,则断开重连之后会自动重新连接)
closeable = false;
// 是否是第一次连接
isFirstConnect = true;
// 消息处理函数
onmessage = Function();
constructor(param: WebSocketParam) {
const { closeable = false, onmessage = Function() } = param;
this.param = param;
this.closeable = closeable;
this.onmessage = onmessage;
}
private connectCallback() {
// 在通道打开的时候就设置一个间隔器用来做心跳检查
this.checkTimer = setInterval(() => {
if (this.wsInstance) {
try {
this.wsInstance.send("alive");
} catch (e) {
this.reconnect();
}
}
}, 15000);
// 分两种情况:首次和非首次连接到后端;两种情况下连接之后像后端发送不同的消息
if (!this.isFirstConnect) {
this.wsInstance.send(
JSON.stringify({})
);
} else {
this.wsInstance.send(JSON.stringify({}));
}
// 修改首次连接的token
this.isFirstConnect = false;
}
// ws实例(wsInstance)发生错误时的回调函数,功能为重新连接
private errorCallback(e) {
this.reconnect();
}
// ws实例关闭事件的回调函数,功能为检测此ws实例能否被关闭,如果不允许关闭则重新连接
private closeCallback() {
if (!this.closeable) this.reconnect();
}
// 建立连接的方法(如果不进行封装,则ws连接会在new WebSocket()也就是WebSocket实例化的时候连接上,这一点非常的不具有语义性,封装之后就好多了)
public connect() {
if (!this.wsInstance) {
// 获得WebSocket的实例化对象
this.wsInstance = new WebSocket(
`ws://${this.param.ip}/ws?token=${localStorage.getItem('token')}` // 一般建立ws都要用凭证的,而凭证就存放在localStorage里面
);
// 设置ws实例对象的onopen回调参数
this.wsInstance.onopen = (e) => {
this.connectCallback();
};
// 设置ws实例对象的onerror回调参数
this.wsInstance.onerror = (e) => {
this.errorCallback(e);
};
// 设置ws实例对象的onopen回调参数
this.wsInstance.onmessage = (e) => {
this.param.onmessage(e.data);
};
// 设置ws实例对象的onclose回调参数
this.wsInstance.onclose = (e) => {
this.closeCallback();
};
}
}
// 重新连接的方法
private reconnect() {
// 如果重新连接功能被禁止,则无需再执行后面的逻辑;
// 锁的目的是为了保证reconnect方法不会被出发多次
if (this.lockReconnect) return;
this.lockReconnect = true;
// ws实例请求连接之后,立即释放重连锁,可勉强是可以的,因为重连方法的入口只有这里一处,这里使用的是一个定时器,所以下一次重连在五秒
setTimeout(() => {
this.destroy();
this.connect();
this.lockReconnect = false;
}, 5000);
}
// 暴露一个发送信息的接口
public sendMsg(msg: any) {
this.wsInstance?.send(msg);
}
// 向外暴露一个取消此封装实体的方法:1. 消除心跳间隔器; 2. 关闭ws实例连接之后删除ws实例对象
public destroy() {
// 处理定时器
clearInterval(this.checkTimer);
this.checkTimer = null;
// 处理ws实例
this.closeable = true;
this.wsInstance.close();
this.wsInstance = null;
// 重置其它参数
this.lockReconnect = false;
this.isFirstConnect = true;
// 没有处理this.params这是因为,params可以在合适的时机继续使用:const newWS = new WSClientFactory(oldWS.params);
}
}
3. 服务端代码
js
const WebSocket = require('ws');
const wss = new WebSocket.Server({
noServer: true
});
wss.on('connection', (ws) => {
console.log('WebSocket connection established.');
// 发送确认连接的消息给客户端
ws.send('Connection established.');
ws.on('message', (message) => {
console.log('Received message:', message);
});
ws.on('close', () => {
console.log('WebSocket connection closed.');
});
});
const server = require('http').createServer();
server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
});
server.listen(8080, '127.0.0.1', () => {
console.log('WebSocket server listening on ws://127.0.0.1:8080');
});
4. 测试用例
js
const param = {
ip: "127.0.0.1:8080", closeable: false, onmessage: function (msg) {
console.log(msg);
}
};
const ws = new WSClientFactory(param);
console.log('ws: ', ws); // ws: WSClientFactory {wsInstance: null, lockReconnect: false, checkTimer: null, closeable: false, isFirstConnect: true, ...}
ws.connect(); // Connection established.
ws.sendMsg('1123213');
ws.destroy();
const ws2 = new WSClientFactory(ws.param);
ws2.connect(); // Connection established.
3. 总结:
WebSocket客户端工厂类的设计思路包括:
- 通过参数接口进行配置
- 使用构造方法进行初始化
- 设置心跳检查定时器
- 处理错误和关闭事件
- 提供发送消息接口
- 销毁连接时的资源释放
这些技巧和实践使得工厂类设计更加合理和可靠,提高了开发效率和代码质量。
同时,这个工厂类的设计也提供了一种可扩展的基础,可以根据实际需求进行进一步封装和定制化。