前言
我们先来看一下心跳检测机制?
心跳检测机制是一种用于检测系统、应用程序或网络连接状态的技术。基本原理是定期发送小型数据包(称为心跳包或心跳消息)以确认远程设备或连接仍然处于活动状态。如果一段时间内没有收到心跳响应,系统就会认为连接已断开或设备不再可用。
心跳机制几个关键概念
- 心跳包:数据包 => 通常是一个标识或消息(ping-pong)
- 心跳间隔:心跳包发送的时间间隔
- 心跳响应:客户端或者服务器接收心跳包时,做出的响应
- 维护状态:心跳检测机制通常在服务器端维护状态
- 超时处理:心跳检测机制认定为不活跃、超时并做处理(自动重连机制)
为什么需要心跳机制?
- 检测连接状态
- 维护连接性
- 管理在线状态
- 检测系统、设备健康状况
- 减少资源浪费
实现思路
- 客户端发出建立连接 new WebSocket(socketUrl)
- 添加连接状态、消息接收、错误信息、关闭等事件监听
- 在socket.onopen 建立连接之后开始定期发送心跳包
提问
为什么需要pong响应机制?
之所以需要 Pong 响应机制,是因为在 WebSocket 连接客户端和服务端之间发送数据时,底层的网络传输有可能会发生数据包丢失或延迟的情况,这可能导致客户端发送的 Ping 数据包无法成功送达服务端。如果只依靠客户端发送 Ping 数据包来保持 WebSocket 连接的活跃状态,那么不能可靠地判断连接是否正常。
Pong 响应机制可以通过检查服务端返回的 Pong 数据包,判断服务端是否正常连接。如果客户端在指定时间内未接收到服务端返回的 Pong 数据包,则可以判断连接已断开,从而执行相应的重连操作,从而保证 WebSocket 连接的稳定性和可靠性,并防止因连接长时间闲置而被服务端断开的情况发生。
是否监听ping响应失败可以执行重连?
ping响应失败无法判断服务端是否断开。有可能是因为网络传输问题(网络差)导致数据包丢失或者延迟情况导致发送失败,所以需要pong响应机制判断服务端的维护状态。
代码1:双向通信
export default class WebsocketClient {
constructor(url, openCallback, errorCallback, msgCallback) {
this.url = url;
this.openCallback = openCallback || function() { };
this.errorCallback = errorCallback || function() { };
this.msgCallback = msgCallback || function() { };
this.socket = null;
this.connected = false;
this.pingInterval = 30; // 发送心跳包的时间间隔,单位为秒
this.pongTimeoutLimit = 3; // 超过 Pong 超时次数的限制值
this.pongTimeoutCount = 0; // 超过 Pong 超时的次数
this.pongTimeout = this.pingInterval + 10; // 判断连接是否正常的超时时间,单位为秒,应该比 pingInterval 稍长,以便确保正常连接不被判定为断开状态
this.lastPingTime = 0; // 上次发送 Ping 数据的时间戳
this.connect();
}
connect() {
const self = this;
this.socket = uni.connectSocket({
url: this.url,
success() {},
fail() {
self.reconnect();
}
});
this.socket.onOpen(() => {
console.log('WebSocket连接成功!');
self.connected = true;
self.openCallback();
self.startPing();
});
this.socket.onMessage((res) => {
console.log('收到服务器消息:' + res.data);
let message = {};
try {
message = JSON.parse(res.data);
} catch (e) {
console.log('消息格式错误!');
}
if (message.action === 'pong') {
console.log('接收到pong数据包,连接正常!');
this.pongTimeoutCount = 0; // pong 超时次数清零
} else {
self.msgCallback(message);
}
});
this.socket.onError(() => {
console.log('WebSocket连接错误!');
self.errorCallback();
self.reconnect();
});
this.socket.onClose(() => {
console.log('WebSocket连接关闭!');
self.connected = false;
self.reconnect();
});
}
startPing() {
const self = this;
this.pingTimer = setInterval(() => {
console.log('发送Ping数据包');
if (self.connected) {
self.socket.send({data: JSON.stringify({action: 'ping'})});
self.lastPingTime = new Date().getTime();
self.pongTimeoutCount++; // 记录pong超时次数
if (self.pongTimeoutCount >= self.pongTimeoutLimit) {
console.log('WebSocket连接已超时(' + self.pongTimeout + '秒内未能接收到pong数据包),正在重新连接...');
self.stop();
self.reconnect();
}
}
}, self.pingInterval * 1000);
}
stop() {
clearInterval(this.pingTimer);
this.pongTimeoutCount = 0;
}
reconnect() {
const self = this;
if (self.connected) {
return;
}
this.stop();
setTimeout(() => {
console.log('正在重新连接 WebSocket...');
self.connect();
}, 5000);
}
close() {
this.stop();
uni.closeSocket();
console.log('WebSocket已关闭!');
}
}
代码2:服务端推送信息
/**
* 发起websocket请求函数
* @param {string} url - websocket请求地址
* @param {number} timeout - 心跳间隔时长,默认5000ms
* @param {function} msgCallback - 接收到ws数据,对数据进行处理的回调函数
*/
export default class WS {
constructor(url, msgCallback, timeout = 30) {
this.url = url;
this.data = null;
// 心跳检测
this.timeout = timeout;
this.interVal = null;
this.reconnectTimer = null;
this.msgCallback =
msgCallback ||
function (res) {
return res;
};
this.connectSocketInit();
}
connectSocketInit() {
this.socketTask = uni.connectSocket({
url: this.url,
success: (res) => {
console.log(this.url + "ws连接成功", res);
}, //请求成功
fail: (res) => {
console.log("ws连接失败", res);
},
});
this.socketTask.onOpen(() => {
console.log("WebSocket连接正常打开中...!");
this.handleHeart();
});
this.socketTask.onMessage((e) => {
console.log("onMessage---------->", e);
//接收消息
try {
let result = JSON.parse(e.data);
this.msgCallback(result);
} catch (err) {
console.log("err", err);
}
});
this.socketTask.onError((e) => {
console.log("取号服务WebSocket连接发生错误", e);
this.reconnect();
});
this.socketTask.onClose(() => {
console.log("关闭websocket");
this.reconnect();
});
}
//心跳检测
handleHeart() {
var that = this;
this.interVal = setInterval(function () {
that.send();
}, this.timeout * 1000);
}
//发送消息
send() {
var that = this;
var data = {
state: 1,
method: "ping",
};
this.socketTask.send({
data: JSON.stringify(data),
fail(res) {
console.log("发送失败,重新连接");
that.reconnect();
},
});
}
// 重新连接socket
reconnect() {
this.clearTimer()
this.reconnectTimer = setTimeout(() => {
this.connectSocketInit();
}, 3000);
}
clearTimer(){
this.reconnectTimer && clearTimeout(this.reconnectTimer);
this.interVal && clearInterval(this.interVal);
}
close() {
this.clearTimer()
uni.closeSocket();
this.socketTask = null;
}
}