Swoole 实践篇之结合 WebSocket 实现心跳检测机制

原文首发链接:Swoole 实践篇之结合 WebSocket 实现心跳检测机制
大家好,我是码农先森。

引言#

前段时间在 Swoole 的交流群里,有群友提问:“如何判断用户端是否在线”。我给予的答案是:“通过在客户端实现心跳包” 来实时记录用户端的心跳数据,最终作为用户是否实时在线的依据。

结合我之前的经验,实现一个简单基于 Swoole 的 WebScoket 服务的心跳检测机制。在用户端会每间隔 5s 上报一次心跳数据,在管理端会每间隔 10 s 获取一次心跳数据,用于实时展示用户的在线状态。

技术实现#

heartbeat.html 用户端页面主要是上报用户的心跳包,当用户在线时会每间隔 5s 上报一次数据,如果关闭掉该页面则会断开连接不再上报数据。

Copy
<!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 会获取一次心跳数据包,用于实时显示用户的状态状态。

Copy
<!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 没有更新心跳数据,如果是则会判定为离线状态。

Copy
<?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; }

总结#

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

posted @   Yxh_blogs  阅读(533)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
历史上的今天:
2017-04-22 php array数组(第一部分)
2017-04-22 php array数组(第二部分)
2017-04-22 javascript字符串函数
点击右上角即可分享
微信分享提示
CONTENTS