原文首发链接: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;
}

总结

这里实现的心跳检测机制是一个基础版的,心跳包的主要作用是用于检测用户端是否存活,有助于我们及时判断用户端是否存在断线的问题。在我之前开发过的项目中,有一个基于物联网在线直播抓娃娃的项目,其中就有需要实时监控设备在线状态的需求,该需求就是使用心跳包来实现的。实际上心跳检测技术,应用更广泛的是实时通信、或设备管理的场景偏多。

Swoole 实践篇之结合 WebSocket 实现心跳检测机制的更多相关文章

  1. netty心跳检测机制

    既然是网络通信那么心跳检测肯定是离不开的,netty心跳检测分为读.写.全局 bootstrap.childHandler(new ChannelInitializer<SocketChanne ...

  2. 面试官:Netty心跳检测机制是什么,怎么自定义检测间隔时间?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,昨天在地里干了一天的 ...

  3. javascript websocket 心跳检测机制介绍

    ====测试代码: ==index.html <!DOCTYPE html> <html lang="en"> <head> <meta ...

  4. Netty 中的心跳检测机制

    心跳检测一般存在于建立长连接 或者 需要保活的场景. 心跳的使用场景 长连接的应用场景非常的广泛,比如监控系统,IM系统,即时报价系统,推送服务等等.像这些场景都是比较注重实时性,如果每次发送数据都要 ...

  5. 分析dubbo心跳检测机制

    目的: 维持provider和consumer之间的长连接 实现: dubbo心跳时间heartbeat默认是60s,超过heartbeat时间没有收到消息,就发送心跳消息(provider,cons ...

  6. Netty — 心跳检测和断线重连

    一.前言 由于在通信层的网络连接的不可靠性,比如:网络闪断,网络抖动等,经常会出现连接断开.这样对于使用长连接的应用而言,当突然高流量冲击势必会造成进行网络连接,从而产生网络堵塞,应用响应速度下降,延 ...

  7. [Swoole系列入门教程 3] 心跳检测

    一.Swoole 的4大知识点: 1.TCP/UDP服务器 2.微服务 3.协程 二.同步与异步: 同步买奶茶:小明点单交钱,然后等着拿奶茶: 异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶 ...

  8. Swoole 心跳检测

    Swoole的心跳检测特别简单,只需要配置 heartbeat_check_interval,heartbeat_idle_time就可以了. heartbeat_check_interval:表示服 ...

  9. WebSocket心跳检测和重连机制

    1. 心跳重连原由 心跳和重连的目的用一句话概括就是客户端和服务端保证彼此还活着,避免丢包发生. websocket连接断开有以下两证情况: 前端断开 在使用websocket过程中,可能会出现网络断 ...

  10. 161114、websocket实现心跳重连

    心跳重连缘由 在使用websocket过程中,可能会出现网络断开的情况,比如信号不好,或者网络临时性关闭,这时候websocket的连接已经断开, 而浏览器不会执行websocket 的 onclos ...

随机推荐

  1. 基于Traefik如何实现向后转发自动去掉前缀?

    前言 Traefik 是一个现代的 HTTP 反向代理和负载均衡器,使部署微服务变得容易. Traefik 可以与现有的多种基础设施组件(Docker.Swarm 模式.Kubernetes.Mara ...

  2. Centos8防火墙配置、端口、进程管理

    Centos8停用.启用.查看当前启用的端口 firewall-cmd --zone=public --add-port=5672/tcp --permanent # 开放5672端口 firewal ...

  3. mysql 重新整理——逻辑架构[二]

    前言 在此简洁逻辑架构,mysql的基础逻辑架构其实和sql server很像的. 正文 首先是客户端,发起了连接. 然后呢连接池后那一块,就是有分析器的那一块,那一块是干啥的呢. 其实我们写的语句呢 ...

  4. Mac搭建appium环境及python运行代码示例

    Appium主要是通过调用安卓提供的接口来执行命令的,所以需要安装Java和安卓SDK. 1.安装Appium服务端 appium的服务端是基于node的,直接使用npm(node包管理器)安装即可, ...

  5. 前端直接获取用户IP的方法

    前端直接获取用户IP的方法 简单来说就是使用两个接口 一个是太平洋网 <script src="//whois.pconline.com.cn/ipJson.jsp?callback= ...

  6. 力扣605(java&python)-种花问题(简单)

    题目: 假设有一个很长的花坛,一部分地块种植了花,另一部分却没有.可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去. 给你一个整数数组  flowerbed 表示花坛,由若干 0 和 1 ...

  7. Log4j漏洞不仅仅是修复,更需要构建有效预警机制

    ​简介:软件的漏洞有时不可避免,根据Gartner的相关统计,到 2025 年,30% 的关键信息基础设施组织将遇到安全漏洞.日志服务SLS,可帮助快速部署一个预警机制,使得漏洞被利用时可以快速发现并 ...

  8. 浏览器扩展开发-Firefox临时载入附加组件(图)

    科普:[FE] Quasar BEX 热加载区别: Chrome vs Firefox 1. 2.  3. Thats all. Refer:Firefox扩展开发 Link:https://www. ...

  9. [MySQL] 原生全文检索 fulltext 的简单应用

    在目标字段上添加全文检索:alter table 表名 add fulltext(字段) with parser ngram 查询语句:select * from xxx where match(字段 ...

  10. dotnet 6 引用 NAudio 的旧版本构建不通过

    本文告诉大家在使用 NAudio 的旧版本导致构建不通过问题,解决方法是升级到 1.10 或以上版本 在更新 dotnet 6 项目时,使用了 NAudio 的旧版本,构建失败,提示 MC1000 如 ...