跟着chatGPT学前端系列-今天学Websocket心跳检测机制!
问题场景
先说一下问题场景,下面是项目的一段封装Webcocket的相关代码
function MyWebSocket(serviceName, config) {
this.serviceName = serviceName;
this.config = config;
this.maxReconnectCount = 2; //最大重连次数
this.curReconnectCount = 0;
this.timer = null;
this.connect();
}
MyWebSocket.prototype.connect = function () {
var _me = this;
var serverIp = location.hostname;
var _config = _me.config;
// 需要判断是否支持websocket,如果不支持,使用flash版本的
if (typeof WebSocket != "undefined") {
initWebSocket();
_me.supportWebSocket = true;
} else {
console.log("not support Websocket");
_me.supportWebSocket = false;
return;
}
function initWebSocket() {
var url = `${_config.params.url}`;
var socket = new WebSocket(url);
_me.socket = socket;
socket.onopen = function (res) {
this.curReconnectCount = 0;
_config.onopen(res);
clearTimeout(this.timer);
};
socket.onmessage = function (message) {
_config.onmessage(message.data);
};
socket.onclose = function (err) {
_config.onclose(err);
};
socket.onerror = function (err) {
_config.onerror(err);
};
}
};
MyWebSocket.prototype.send = function (message) {
if (this.supportWebSocket) {
this.socket.send(JSON.stringify(message));
} else {
this.socket.sendRequest(this.serviceName, message, true);
}
};
MyWebSocket.prototype.close = function () {
if (this.supportWebSocket) {
this.socket.close();
this.timer && clearTimeout(this.timer);
} else {
this.socket.disconnect();
}
};
MyWebSocket.prototype.reconnect = function () {
this.timer = setTimeout(() => {
this.curReconnectCount++;
if (this.curReconnectCount > this.maxReconnectCount) {
clearTimeout(this.timer);
return;
}
// if (this.curReconnectCount > this.maxReconnectCount) return;
console.log("ws reconnect time", this.curReconnectCount);
this.close();
this.connect();
}, 5000);
};
export default MyWebSocket;
我:你能帮我分析上面这段代码吗?希望讲解得越详细越好
ChatGPT:当然可以。
总结代码功能
这段代码是一个使用 JavaScript 实现的 WebSocket 类,用于与后端建立 WebSocket 通信,具有以下功能:
- 支持使用 WebSocket 协议建立连接,并能在连接关闭后自动重连;
- 当浏览器不支持 WebSocket 时,使用 flash 版本的 WebSocket;
- 可以发送数据到后端,并能处理接收到的数据;
- 可以关闭连接。
下面是对代码的详细分析:
构造函数
function MyWebSocket(serviceName, config) {
this.serviceName = serviceName;
this.config = config;
this.maxReconnectCount = 2; //最大重连次数
this.curReconnectCount = 0;
this.timer = null;
this.connect();
}
构造函数用于初始化 WebSocket 对象,它接收两个参数:
-
serviceName
:服务名称,这个参数在后续处理中没有使用到,可能是留给以后扩展使用的; -
config
:该参数是一个对象,包含了 WebSocket 对象的配置信息,它有以下属性:params.url
:WebSocket 连接的 url;onopen
:WebSocket 连接成功后的回调函数;onmessage
:接收到 WebSocket 收到消息时的回调函数;onclose
:WebSocket 连接关闭时的回调函数;onerror
:WebSocket 连接发生错误时的回调函数。
可以看出,配置对象中的回调函数是用于处理 WebSocket 连接不同状态下的消息和事件的。
构造函数还初始化了一些属性:
maxReconnectCount
:最大连接重连次数;curReconnectCount
:当前已经重连的次数;timer
:用于计算重连间隔的计时器,这个计时器在后面会被用到;- 然后调用了
connect
方法来建立 WebSocket 连接。
connect 方法
MyWebSocket.prototype.connect = function () {
var _me = this;
var serverIp = location.hostname;
var _config = _me.config;
// 需要判断是否支持websocket,如果不支持,使用flash版本的
if (typeof WebSocket != "undefined") {
initWebSocket();
_me.supportWebSocket = true;
} else {
console.log("not support Websocket");
_me.supportWebSocket = false;
return;
}
// ...
};
connect
方法首先保存了当前对象的引用 this
为 _me
,并将 _me.config
保存为 _config
变量。然后它判断当前浏览器是否支持 WebSocket 协议,如果支持,则调用 initWebSocket
初始化 WebSocket 连接对象,并将当前对象的 supportWebSocket
属性设置为 true
。如果浏览器不支持 WebSocket,则直接返回并将 supportWebSocket
属性设置为 false
。
initWebSocket 方法
function initWebSocket() {
var url = `${_config.params.url}`;
var socket = new WebSocket(url);
_me.socket = socket;
socket.onopen = function (res) {
this.curReconnectCount = 0;
_config.onopen(res);
clearTimeout(this.timer);
};
socket.onmessage = function (message) {
_config.onmessage(message.data);
};
socket.onclose = function (err) {
_config.onclose(err);
};
socket.onerror = function (err) {
_config.onerror(err);
};
}
initWebSocket
方法创建了一个 WebSocket 对象,并将其保存到当前对象的 this.socket
中。然后它为 socket
对象的 onopen
、onmessage
、onclose
、onerror
事件分别绑定了处理函数,分别是 _config.onopen
、_config.onmessage
、_config.onclose
、_config.onerror
这几个回调函数,分别用于处理 WebSocket 连接建立、接收到消息、连接关闭和出现错误的情况。
注意,this.curReconnectCount
和 this.timer
是定义在 WebSocket 对象上的属性,应该改为 _me.curReconnectCount
和 _me.timer
。
send 方法
MyWebSocket.prototype.send = function (message) {
if (this.supportWebSocket) {
this.socket.send(JSON.stringify(message));
} else {
this.socket.sendRequest(this.serviceName, message, true);
}
};
send
方法用于向服务器发送数据,它接收一个参数 message
,表示要发送的消息。如果当前浏览器支持 WebSocket,则直接将 message
序列化为 JSON 字符串后调用 this.socket.send
方法发送数据;否则,将调用 this.socket.sendRequest
方法发送数据。
close方法
MyWebSocket.prototype.close = function () {
if (this.supportWebSocket) {
this.socket.close();
this.timer && clearTimeout(this.timer);
} else {
this.socket.disconnect();
}
};
close
方法用于关闭 WebSocket 连接。如果当前浏览器支持 WebSocket,则调用 this.socket.close
方法关闭连接并清除 this.timer
计时器;否则调用 this.socket.disconnect
方法关闭连接。
reconnect 方法
MyWebSocket.prototype.reconnect = function () {
this.timer = setTimeout(() => {
this.curReconnectCount++;
if (this.curReconnectCount > this.maxReconnectCount) {
clearTimeout(this.timer);
return;
}
// if (this.curReconnectCount > this.maxReconnectCount) return;
console.log("ws reconnect time", this.curReconnectCount);
this.close();
this.connect();
}, 5000);
};
reconnect
方法用于 WebSocket 连接断开后的自动重连功能。它首先设置了一个计时器,5 秒后执行重连操作。重连操作会先判断已经重连的次数是否大于最大重连次数,如果是则直接返回;否则,计数器加一,然后执行关闭连接和重新连接操作。注意,在执行重连操作前,计时器 this.timer
需要被清除。
我:这边我想问下,函数直接返回意味着什么?
ChatGPT:当函数直接返回时,意味着该函数在执行到该处时立即结束其执行并返回,然后不再执行之后的代码。具体返回的内容由函数实现决定,可能是一个值,也可能是一个对象或者一个 undefined。在函数中可以使用 return 语句来实现直接返回。如果函数没有使用 return 语句返回任何值,它将默认返回 undefined。
在代码中,当浏览器不支持 WebSocket 时,connect
方法会直接返回,不再执行之后的代码。因为在这种情况下,后面的代码都依赖于 WebSocket 连接,而该连接又因为不支持导致无法建立,所以这里无法继续执行,进行任何操作都是无意义的。因此,直接返回是一种简单有效的处理方式。
我:我明白了,我还想问一下,为什么明明可以可以直接在websocket的那些内置方法(如open、close等)中实现心跳检测机制,在上面的代码中他却要重新定义一个构造函数,在其原型对象上重写connect这些方法呢?
ChatGPT:WebSocket 的内置方法,如 onopen
、onclose
等,确实可以用来实现心跳机制,也可以用来处理 WebSocket 连接的状态变化。但是在实际开发中,为了代码的可重用性和灵活性,我们通常会自己封装一个 WebSocket 类,定义自己的方法和属性,在此基础上实现心跳检测等功能。
在上面的代码中,MyWebSocket
就是一个自定义的 WebSocket 类,通过重写 connect
、reconnect
、send
、close
等方法,实现了 WebSocket 的连接、重连、数据发送和关闭功能,并且加入了重连和心跳检测的自动处理。这样,我们就可以在实际应用中直接使用 MyWebSocket
类创建对象,而不需要每次都写一遍 WebSocket 连接的细节。而在 MyWebSocket
类中重新定义这些方法,并在 prototype
对象中进行修改,不会对浏览器原始的 WebSocket 实现造成影响,也符合了面向对象编程的思想。
我:那这样自己封装一个 WebSocket 类有什么好处吗?
ChatGPT:自己封装一个 WebSocket 类,其好处主要有以下几个:
- 更灵活的控制权:使用自己封装的 WebSocket 类,可以更加灵活地控制 WebSocket 的通信。可以在 sendMessage 或 onMessage 等方法中加入自己的逻辑,实现自己想要的功能,比如协议解析、数据转换等等。
- 更好的封装和复用性:使用自己封装的 WebSocket 类,可以让代码更好的封装和复用。WebSocket 是一个经常被使用的通信协议,如果每个应用程序都自己实现 WebSocket,那么代码会重复很多,而自己封装一个 WebSocket 类可以让代码更好的封装和复用,减少了代码重复。
- 可维护性和可扩展性:使用自己封装的 WebSocket 类,可以增加代码的可维护性和可扩展性。比如,如果后期需要增加一些新的功能或者修改一些原有的功能,可以直接修改 WebSocket 类的内容,而不需要修改使用 WebSocket 类的所有代码。
- 更加易于测试:自己封装的 WebSocket 类可以更加轻松地进行测试,可以模拟 WebSocket 连接、收发数据等情况,有效地保证代码的质量。如果直接使用原生的 WebSocket 对象,测试起来会比较麻烦和困难。
综上所述,自己封装一个 WebSocket 类可以提高代码的复用性和可维护性,减少代码重复,提高代码的可测试性和稳定性,让使用和维护 WebSocket 更加方便和自如。
我:我还是看不出自己封装过一个websocket类比起直接使用websocket有什么好处,能不能举一个通俗易懂且能够有力证明的例子?
ChatGPT:好的,下面我举一个例子来说明自己封装 WebSocket 类的好处:
假设你开发了一个在线聊天系统,在这个系统中需要使用 WebSocket 来实现实时通讯功能。使用原生支持的 WebSocket 对象可以实现这个功能,但是如果需要在聊天室中实现@用户并弹出提示的功能,或者实现表情包和图片等高级功能,原生的 WebSocket 对象就没有这个能力,因此就需要进行扩展。而这些扩展需要在通信开始之前、消息处理时或通信结束时进行处理,这就需要对 WebSocket 对象进行进一步的封装。
如果你在系统中自己建立一个 MyWebSocket 类,封装了 WebSocket 的所有功能和扩展,并且在代码的其他部分直接调用 MyWebSocket 类的方法来处理 WebSocket 通信,就可以实现代码的快速开发和重用,代码的可读性、扩展性和可维护性也都会得到提高。
比较具体地,下面给出一个示例代码,假设这个示例代码要实现的是在聊天室中@用户并弹出提示的功能:
// 基于 MyWebSocket 封装的 WebSocket 类,增加了 @ 得到提示的功能
class MyWebSocketWithTag extends MyWebSocket {
constructor(serviceName, config) {
super(serviceName, config);
}
// 用户发送消息时,解析出 @ 的用户名
parseMessage(text) {
const usernames = ['user1', 'user2', 'user3'];
const regex = /@(user\d+)/;
const result = regex.exec(text);
if (result) {
const username = result[1];
if (usernames.includes(username)) {
return { username, text: text.replace(regex, '') };
}
}
return { text };
}
// 发送消息时,判断是否要@用户
send(message) {
const parsedMessage = this.parseMessage(message.text);
if (parsedMessage.username) {
// 如果消息中有 @ 的用户名,则通过 WebSocket 发送消息,并额外发送一个提示消息
super.send(parsedMessage);
const tipMessage = `您收到了一条来自 ${message.username} 的@消息!`;
super.send({ username: parsedMessage.username, text: tipMessage });
} else {
// 否则,直接通过 WebSocket 发送消息
super.send(message);
}
}
}
可以看到,自己封装 WebSocket 类后,可以根据具体的需求进行扩展(在这个示例中是增加了 @ 得到提示的功能),并且添加功能时只需修改封装类的代码即可,对其它代码没有影响,使得代码更加清晰简单,易于维护和扩展。
如果不进行封装,则需要在使用 WebSocket 的地方写一些额外的代码,处理 WebSocket 带来的一些细节问题,而这样的代码会和聊天系统的业务逻辑混杂在一起,使得整个系统结构复杂、难以维护。
我:那这个心跳检测机制是怎么检测出断线的?
ChatGPT:在 WebSocket 通信中,心跳机制是通过服务器定时向客户端发送心跳消息,在客户端超过一定时间未收到心跳消息时,认为服务器已经断开了 WebSocket 连接,从而进行重连等操作。具体实现可以分为两部分,下面分别介绍:
- 服务器定时向客户端发送心跳消息
在 WebSocket 通信过程中,服务器可以通过持续发送空消息或特定内容的消息来实现心跳功能。(这里以空消息为例)
//在 WebSocket 中使用心跳定时器
var intervalID = window.setInterval(function () {
if (socket.readyState === WebSocket.OPEN) {// 只有在连接状态下才能发送心跳
socket.send("");
} else {// 如果没有心跳信号,则说明连接已经断开
clearInterval(intervalID);
}
}, 5000);// 每 5 秒发送一次心跳信号
- 客户端检测心跳消息
客户端可以通过设置定时器,在接收到心跳消息后,及时重置定时器。当定时器超时时,认为服务器已经断开 WebSocket 连接,进行重连等操作。
// 客户端检测心跳
var beatInterval = 10000; // 定义心跳时间间隔为 10 秒
var heartCheck = {
// 发送空包
timeoutObj: null,
// 定时器
serverTimeoutObj: null,
// 重置心跳定时器
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
this.start();
},
// 开始心跳定时器
start: function () {
var self = this;
this.timeoutObj = setTimeout(() => {
console.log("heartbeat");
socket.send("");
// 发送心跳后,开始监听是否超时
self.serverTimeoutObj = setTimeout(() => {
// 超时,则说明连接已经断开,进行重连处理
console.log("heart check timeout");
self.reconnect(); // 重连
}, beatInterval);
}, beatInterval);
},
};
看到这里,根据ChatGPT给的例子,其实已经可以判断出来,最初想让他帮我分析的代码中,检测断线的方式是第二种,客户端主动检测心跳消息
啊不对,我好像根本就没写怎么检测断线的。。。 😅 😅
我:在我上面的代码中,是怎么检测断线的?用的哪种方式?(下面GPT的回答基于我原先的代码给出了一个完善后的例子)
ChatGPT:在上面的代码中,通过在 MyWebSocket
类的构造函数中定义了一个心跳定时器,实现了心跳检测。在每次发送数据时,如果 WebSocket 连接处于打开状态,就会重置心跳定时器,如果心跳超时,则会调用 reconnect
方法重新连接 WebSocket。
具体实现如下:
class MyWebSocket {
constructor(serviceName, config = {}) {
this.serviceName = serviceName;
this.config = config;
this.protocol = config.protocol || 'ws';
this.reconnectCount = 0; //重连次数
this.timer = null; //心跳定时器
this.ws = null;
this.connect();
}
/**
* WebSocket 连接方法
*/
connect() {
const wsUrl = `${this.protocol}://${this.serviceName}`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = (event) => {
console.log('WebSocket 已连接!');
this.reconnectCount = 0; //重置重连计数器
this.startHeartbeat(); //开始心跳
if (this.config.onOpen) {
this.config.onOpen(event);
}
};
this.ws.onerror = (event) => {
console.log('WebSocket 连接发生错误!', event);
if (this.config.onError) {
this.config.onError(event);
}
};
this.ws.onmessage = (event) => {
if (this.config.onMessage) {
this.config.onMessage(event);
}
};
this.ws.onclose = (event) => {
console.log('WebSocket 连接已关闭!', event);
this.stopHeartbeat(); //停止心跳
if (this.config.onClose) {
this.config.onClose(event);
}
this.reconnect();
};
}
/**
* WebSocket 重连方法
*/
reconnect() {
if (this.reconnectCount < this.config.maxReconnectCount) {
this.reconnectCount++;
console.log(`WebSocket 第 ${this.reconnectCount} 次重连...`);
setTimeout(() => {
this.connect();
}, this.config.reconnectInterval);
} else {
console.log(`WebSocket 重连超过 ${this.config.maxReconnectCount} 次,已放弃重连!`);
}
}
/**
* WebSocket 心跳检测方法
*/
startHeartbeat() {
this.timer = setInterval(() => {
if (this.ws.readyState == WebSocket.OPEN) {
console.log('WebSocket 心跳检测...');
this.ws.send('');
// 避免重复打开定时器
if (this.timer == null) return;
} else {
console.log('WebSocket 已断开连接');
}
}, this.config.heartbeatInterval);
}
/**
* WebSocket 停止心跳方法
*/
stopHeartbeat() {
clearInterval(this.timer);
this.timer = null;
}
/**
* 发送 WebSocket 消息
* @param message 消息对象
*/
send(message) {
if (!message) {
console.log('WebSocket 消息不能为空!');
return;
}
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
this.startHeartbeat(); //重置心跳定时器
} else {
console.log('WebSocket 连接未打开,不能发送消息!');
}
}
/**
* 关闭 WebSocket 连接
*/
close() {
this.stopHeartbeat(); //停止心跳
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
}
}
在上面的代码中,实现了 startHeartbeat
和 stopHeartbeat
方法来控制心跳检测。在 startHeartbeat
方法中,使用 setInterval
创建一个定时器,在每次发送数据时,如果 WebSocket 连接处于打开状态,就会重置心跳定时器。如果超过了设定的心跳时间,定时器就会执行 reconnect()
方法,重新连接 WebSocket。
总之,上面的代码通过封装实现了 startHeartbeat
方法和 stopHeartbeat
方法实现了心跳检测,并在每次发送数据时检测 WebSocket 的状态,从而进行心跳检测和重连操作。
我:那这两种断线检测的方式有什么区别?
ChatGPT:两种断线检测方式的区别主要在两个方面:
- 检测的角度不同
心跳机制是在客户端设定一个定时器,定时向服务器发送心跳消息,同时按照一定的时间内未收到心跳消息来判断服务器的连接状态。
而 onclose
事件是服务器返回一个关闭消息后,客户端才能得知WebSocket已断线。
- 触发检测的方式不同
心跳机制是由客户端主动向服务器发送心跳消息,在发送心跳消息时判断 WebSocket 连接是否断开,是一个检测机制,可以快速检测到 WebSocket 的连接状态。
... ...
我:不是,我指的是服务器定时向客户端发送消息和客户端主动检测心跳这两种检测断线的方式有什么区别?
ChatGPT:这两种方式有以下区别:
- 定时向客户端发送消息:服务器会定时向客户端发送确认消息,以确保客户端仍然在线。如果客户端在规定时间内未能响应,则服务器可以认为客户端已经断线。这种方式的优点是服务器可以很快地检测到客户端掉线,但是定时发送消息会占用一定的网络带宽和服务器资源。
- 客户端主动检测心跳:客户端会定时向服务器发送心跳消息,以确保服务器仍然在线。如果服务器在规定时间内未能响应,则客户端可以认为服务器已经断线。这种方式的优点是客户端可以自主控制心跳消息的发送频率,减少网络带宽的占用,但是客户端断开时,服务器可能无法及时检测到。
综上所述,两种方式各有优缺点,可以根据实际情况选择适合的方式进行断线检测。如果网络带宽和服务器资源都比较充足,可以考虑使用服务器定时发送确认消息的方式。如果网络带宽有限或者需要减少服务器资源的占用,则可以考虑使用客户端主动检测心跳的方式。
看到这里,大家应该也和我一样,学会了Websocket心跳检测机制的方法了叭~~
真的不得不说,ChatGPT是个很棒的老师和伙伴! 😽 😽大家平时也可以利用好ChatGPT这个工具,来辅助我们学习或者做项目,都是事半功倍滴!!!
转载自:https://juejin.cn/post/7236314072765202488