技术标签: Swoole实践篇 swoole websocket php 后端
原文首发链接:Swoole 实践篇之结合 WebSocket 实现心跳检测机制
大家好,我是码农先森。
前段时间在 Swoole 的交流群里,有群友提问:“如何判断用户端是否在线”。我给予的答案是:“通过在客户端实现心跳包” 来实时记录用户端的心跳数据,最终作为用户是否实时在线的依据。
结合我之前的经验,实现一个简单基于 Swoole 的 WebScoket 服务的心跳检测机制。在用户端会每间隔 5s 上报一次心跳数据,在管理端会每间隔 10 s 获取一次心跳数据,用于实时展示用户的在线状态。
heartbeat.html
用户端页面主要是上报用户的心跳包,当用户在线时会每间隔 5s 上报一次数据,如果关闭掉该页面则会断开连接不再上报数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Heartbeat Example</title>
</head>
<body>
<script>
var socket = new WebSocket('ws://127.0.0.1:9502');
var user_id = getQueryVariable("user_id")
socket.onopen = function() {
console.log('WebSocket 已连接');
setInterval(function() {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({type: 'SetHeartbeat', user_id: user_id, user_name: "码农先森"+"(" + user_id + ")", "timestamp": Math.floor(Date.now() / 1000)}));
}
}, 5000); // 每隔5秒发送一次心跳数据
};
socket.onerror = function(error) {
console.error('WebSocket 错误:' + error);
};
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭:' + event.code + ', ' + event.reason);
};
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
</script>
</body>
</html>
admin.html
管理端页面主要是展示用户的在线状态,每间隔 10s 会获取一次心跳数据包,用于实时显示用户的状态状态。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Admin Example</title>
<style>
#json-data{
text-align: center;
border: 1px solid blue;
width: 30%;
}
</style>
</head>
<div id="json-data"></div>
<body>
<script>
var socket = new WebSocket('ws://127.0.0.1:9502');
socket.onopen = function() {
console.log('WebSocket 已连接');
setInterval(function() {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({type: 'GetHeartbeat', user: "admin"}));
}
}, 10 * 1000); // 定时每10s获取一次心跳数据
};
socket.onmessage = function(e) {
const jsonData = JSON.parse(e.data);
const container = document.getElementById('json-data');
while (container.firstChild) {
container.removeChild(container.firstChild);
}
jsonData.forEach(item => {
const div = document.createElement('div');
div.innerHTML = `<p>用户ID: ${item.user_id}, 用户名称: ${item.user_name}, 状态: ${item.status}</p>`;
container.appendChild(div);
});
};
socket.onerror = function(error) {
console.error('WebSocket 错误:' + error);
};
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭:' + event.code + ', ' + event.reason);
};
</script>
</body>
</html>
websocket_server.php
服务主要是用于接收用户端上报的心跳数据,以及推送用户的心跳数据到管理端页面;心跳数据会存储到 Redis 缓存中,便于更新数据,在推送数据时会判断用户是否超过 30s 没有更新心跳数据,如果是则会判定为离线状态。
<?php
Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL);
// 创建 WebSocket 服务
$server = new Swoole\WebSocket\Server("0.0.0.0", 9502);
// 监听 WebSocket 连接事件
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
echo "新的客户端连接: {$request->fd}\n";
});
// 监听 WebSocket 消息事件
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
$data = json_decode($frame->data, true);
if ($data["type"] == "SetHeartbeat") {
echo "接收到了用户[{$data["user_name"]}]的心跳包\n";
setHeartbeatCache($data);
}
if ($data["type"] == "GetHeartbeat") {
$data = getHeartbeatCache();
$results = [];
foreach($data as $val) {
$val = json_decode($val, true);
$resutl["user_id"] = $val["user_id"];
$resutl["user_name"] = $val["user_name"];
$resutl["status"] = "在线";
// 超过 30 秒没有心跳包, 则离线
if (time() - $val["timestamp"] > 30) {
$resutl["status"] = "离线";
}
$results[] = $resutl;
}
$server->push($frame->fd, json_encode($results));
}
});
// 监听 WebSocket 关闭事件
$server->on('close', function ($ser, $fd) {
echo "客户端 {$fd} 关闭连接\n";
});
// 启 WebSocket 服务
$server->start();
// 设置缓存
function setHeartbeatCache($data) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->hSet('heartbeat', $data["user_id"], json_encode($data));
$redis->close();
}
// 获取缓存
function getHeartbeatCache() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$data = $redis->hGetAll('heartbeat');
$redis->close();
return $data;
}
这里实现的心跳检测机制是一个基础版的,心跳包的主要作用是用于检测用户端是否存活,有助于我们及时判断用户端是否存在断线的问题。在我之前开发过的项目中,有一个基于物联网在线直播抓娃娃的项目,其中就有需要实时监控设备在线状态的需求,该需求就是使用心跳包来实现的。实际上心跳检测技术,应用更广泛的是实时通信、或设备管理的场景偏多。
文章浏览阅读1k次。1、介绍自动曝光技术可以分成三步骤,分别是亮度获取、亮度分析和曝光调节。这一章内容主要介绍一下自动曝光技术中的亮度获取方法。2、自动曝光技术主要分为 光学_相机 目标亮度是什么
文章浏览阅读488次。详情请参照下方博客:博客地址本人亲测有效,不需要安装其他程序,复制好脚本,直接执行即可_centos7 redash 安装
文章浏览阅读1k次,点赞21次,收藏15次。【代码】【Python】科研代码学习:十 evaluate (metrics,Evaluator)_python evaluate
文章浏览阅读1.3k次。于是接收方一共收到3个ack=3,于是马上重发3号数据包,然后接收方接收到了3号数据包,此时由于4,5号数据包也已经被接收了,所以发送ack=6,表示接下来希望接收序号为6的数据包。但是3由于某些原因丢失了,没发到接收方那里,4到达了,于是接收方继续发送一个ack=3。1,2先到了,于是接收方返回一个ack=3,表示接下来想要接受一个序号为3的数据包。然后5又到达了,接收方继续发送一个ack=3。发送方发送1,2,3,4,5 一共五份数据。_tcp乱序的解决办法
文章浏览阅读2.4w次。问题:自从升级到了鸿蒙系统之后,在看抖音时经常会出现提示“当前无网络,请检查后重试”,WIFI已经确认过了,无任何问题,该如何处理?问题追加:要么出现如下图提示:要么出现如下图提示:网络不一样,但都是提示“当前无网络,请检查后重试”。回答:建议尝试还原下网络,有用户重置还原网络设置改善了好多,另外,请把系统更新至新版。注:有不少用户也有此情况,以下是他们的反馈:1、可能是跟鸿蒙系统本身有关,因为原..._鸿蒙系统状态网络不可用
QT框架基本认识
C语言函数指针数组的使用方法非常重要,它可以存储多个函数指针,实现灵活的函数调用。通过函数指针数组,我们可以在运行时根据需求选择调用不同的函数。这种技术在编写大型程序时尤为有用。
文章浏览阅读3.1k次,点赞19次,收藏40次。题目链接:普通并查集:1、2、3文章目录1.Wireless Network POJ - 22362.The Suspects POJ - 16113.How Many Tables HDU - 12131.Wireless Network POJ - 2236将所有可以互相连通的电脑放在一个集合中对于O操作(修电脑),将该电脑与所有已经修好的电脑判断一下,若距离小于D,则合并集合对于S操作(查询),直接查询两个电脑是否属于同一个集合即可#include <iostream>#i_nefu_qky并查集
文章浏览阅读1.5k次。如何参加兴趣小组呢,那就要写一份申请书,这里给大家分享一些关于参与兴趣小组申请书范文,方便大家学习。参与兴趣小组申请书1时光流逝,转眼又一个学期过去了本学年的美术兴趣小组活动也得以告终,美术兴趣小组活动总结。本着贯彻国家教育方针,实施素质教育的原则,使学生掌握知识技能,发展智力,形成个性品德的目的,使我们的美术兴趣小组顺利开放。其主要目标就是对学生进行审美教育,培养学生健康、正确的审美观念,具有高..._加入兴趣小组的理由
文章浏览阅读6.1k次。通过java API 连接到基于kerberos安全验证的Hbase_hbaseconfig.set
文章浏览阅读615次。该楼层疑似违规已被系统折叠隐藏此楼查看此楼直接贴代码(这是在网上找的然后改的复制可以运行):public static void main(String[] args) throws SQLException {OracleDataSource dataSource = new OracleDataSource();dataSource.setUser("...");dataSource.set..._java实时监听oracle表
文章浏览阅读4.1k次,点赞2次,收藏36次。1.什么是UI框架2.为什么要有UI框架3.如何使用UI框架,使用UI框架的不同方案比较4.UI框架源码实现5.UI框架关键点,重要节点,疑难杂症场景_游戏ui框架设计