一、什么是心跳检测机制
心跳检测机制是一种用于检测系统、应用程序或网络连接状态的技术。基本原理是定期发送小型数据包(称为心跳包或心跳消息)以确认远程设备或连接仍然处于活动状态。如果一段时间内没有收到心跳响应,系统就会认为连接已断开或设备不再可用。本文将用来实现多人聊天室在线人数的统计。
1、涉及的概念
(1)心跳包:数据包,通常是一个标识或消息。
(2)心跳间隔:心跳包发送的时间间隔。
(3)心跳响应:客户端或者服务器接收心跳包时,做出的响应。
(4)维护状态:心跳检测机制通常在服务器端维护状态。
(5)超时处理:心跳检测机制认定为不活跃、超时并做处理。
2、心跳机制的作用
(1)检测连接状态
(2)维护连接性
(3)管理在线状态
(4)检测系统、设备健康状况
(5)减少资源浪费
二、实现思路
以多人聊天室的中心跳检测机制为例,需要以下步骤:
(1)客户端和服务器端建立连接(websocket)
(2)在客户端定期轮询向服务器端发送心跳包 ping。
(3)服务器端接收心跳包并做出响应 pong。
(4)服务器端维护 ping 的客户端列表,返回活跃的客户端数。
(5)客户端接收到心跳响应,更新自己状态,如心跳包异常,则做异常处理。
三、前端部分
聊天室的搭建参考文章,《WebSocket、Vue 3 和 Node.js 实现多人实时交流平台》
1、客户端发出建立连接 new WebSocket(socketUrl)
2、添加连接状态、消息接收、错误信息、关闭等事件监听
let socket = null;
/**
* 建立连接
*/
const connectWebSocket = () => {
const socketUrl = config?.baseWsUrl + "/ws"; // 服务端地址
// 创建实例
socket = new WebSocket(socketUrl);
// 监听WebSocket连接成功事件
socket.onopen = () => {
};
//
socket.onmessage = async (event) => {
};
// 错误监听
socket.onerror = (msg) => {
};
// 关闭监听
socket.onclose = (msg) => {
};
};
### 发送心跳包
必须在建立连接成功之后
3、在socket.onopen 建立连接之后开始定期发送心跳包ping
(1)定时器用全局变量保存,启动前清理上一个定时器。
(2)页面卸载前也清理。
let timer = null
// 监听WebSocket连接成功事件
socket.onopen = () => {
console.log('WebSocket连接成功!')
connected.value = true;
retry = 0
clearInterval(timer);
timer = setInterval(() => {
sendMessage("ping");
}, 1000 * 6);
sendMessage(true);
};
/**
* 客户端向服务器发送消息
* type: 'ping' 就是发送的心跳包
*/
const sendMessage = (type = "") => {
if (!socket) return;
const state = { };
const messageObj = { // 消息对象
...state,
type
};
socket.send(JSON.stringify(messageObj));
};
4、自动重连机制
(1)自动重连触发:webSocket closes事件、心跳机制检测不活跃。
(2)次数控制:自动重连次数控制,超过了提示用户手动重连。
(3)连接成功重置次数限制。
注意:定时器的回收,避免造成内存泄漏
let retry = 0
/**
* 重连控制函数
* @param {*} automatic 是否手动重连
* @param {*} retryCunt
*/
const reConnectWebSocket = (isAutomatic=false, retryCunt=10) => {
flag = false;
if (!automatic) {
console.log(retry, "自动重连次数")
retry++
if (retry >= retryCunt) {
clearInterval(timer);
connected.value = false;
socket = null;
return
}
} else {
console.log('手动重连')
}
connectWebSocket();
};
5、用户关闭页面
当用户直接关闭页面时,监听beforeunload事件 给服务器发送消息更新状态。
const onLoadHandle = async() => {
$fetch(`${config?.baseUrl}/updateInfo`, {
method: "POST",
params: {
name: props.state?.name,
roomId: props.state?.roomId,
},
});
};
onMounted(() => {
window.addEventListener("beforeunload", onLoadHandle);
});
四、后端部分
1、建WebSocket服务器。
2、接收建立连接请求并进行消息接收、错误信息、关闭等事件监听。
3、判断接收到的消息类型为心跳包ping,则响应pong, 记录时间。
4、响应前再判断一下响应pong时间是否过期,过期则,标记不活跃(一般比客户端的轮询间隔长一点就行)
5、用户直接退出、关闭网页=> 视为退出房间。
const WebSocket = require('ws');
const http = require('http');
const express = require('express');
const app = express();
/**
* Map(7) {
'1111' => {
roomId: '1111',
createUser: '1',
createTime: 1693574119564,
password: '',
userList: [
[Object]
],
messageList: [
[Object]
]
},
*/
let roomMap = new Map() // 房间信息存储对象
// 创建一个HTTP服务器
const server = http.createServer(app);
// 创建WebSocket服务器并将其附加到HTTP服务器
const wss = new WebSocket.Server({ noServer: true });
server.on('upgrade', (request, socket, head) => {
switch (request.url) {
case '/ws':
// 只允许 这个主机下的请求访问
if (
// true||
request.headers.origin.includes('localhost')
) {
wss.handleUpgrade(request, socket, head, (ws) => {
Socket = socket
wss.emit('connection', ws, request);
});
} else {
console.log(
'Unauthorized request from:',
request.headers.origin
);
}
break;
default:
break;
}
});
// 监听WebSocket连接事件
wss.on('connection', (socket) => {
console.log('WebSocket 连接已建立');
// 监听客户端发送的消息
socket.on('message', async(message) => {
const msg = JSON.parse(message);
const { type } = msg || {};
const room = roomMap.get(roomId);
const time = new Date().getTime()
if (type == 'create') {
} else if (type === 'join') { // 处理用户加入房间
} else if (type === 'leave') { // 处理用户离开房间
} else if(type === 'ping'){ // 处理心跳检测机制
/**
* 1 判断消息类型为 心跳检测包
* 2. 响应pong
* 3. 标记用户信息并记录时间active:true && activeTime = time
* 4. 返回消息时遍历房间的用户列表信息 根据活跃时间判断是否为活跃
* 5. 返回更新后的状态
*/
msg.status = 'pong'
room.userList.some(item => {
if (item.name === name) {
// 用户活跃
item.active = true
item.activeTime = time
console.log('ping', JSON.stringify(item))
}
})
} else { // 处理其它逻辑
}
// 用户列表
msg.users = roomMap.get(roomId)?.userList?.length || 0;
// 用户数
msg.totalUserList = roomMap.get(roomId)?.userList || []
// 活跃数===>>> 遍历房间的用户列表信息 根据活跃时间判断是否为活跃
msg.activityUsers = roomMap.get(roomId)?.userList?.filter(item => {
if (item.active && item.activeTime && (time - item.activeTime) < 1000 * 8) {
item.active = true
return true
} else {
item.active = false
item.activeTime = 0
}
})?.length
// 广播消息给所有连接的客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg));
}
});
});
wss.on('error', (message) => {
console.log('报错了');
});
wss.on('close', (message) => {
console.log('连接关闭:')
})
wss.on('open', (message) => {
console.log('open', message)
})
});
// 启动HTTP服务器
server.listen(3000, () => {
console.log('HTTP服务器已启动,监听端口3000');
});
五、总结
以上就是整个心跳机制的核心实现,具体细节可以参考多人聊天室心跳检测机制源码。
(1)心跳机制:心跳检测是用于监测连接状态,通过定期发送心跳消息来确认连接的活跃性。在聊天室中,我们可以定期发送心跳消息,以检测用户是否在线。
(2)自动重连:自动重连机制是用于在连接断开时自动尝试重新建立连接。
(3)提高体验:使用这两个机制,用户更可靠保持在线状态,无需手动重连,有效提高了用户体验。