Swoole 实践篇之结合 WebSocket 实现心跳检测机制-程序员宅基地

技术标签: 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;
}

总结

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yxhbk/article/details/138095135

智能推荐

自动曝光技术—亮度获取_相机 目标亮度是什么-程序员宅基地

文章浏览阅读1k次。1、介绍自动曝光技术可以分成三步骤,分别是亮度获取、亮度分析和曝光调节。这一章内容主要介绍一下自动曝光技术中的亮度获取方法。2、自动曝光技术主要分为 光学_相机 目标亮度是什么

Centos 7 一键安装Redash (Centos7 + Docker)_centos7 redash 安装-程序员宅基地

文章浏览阅读488次。详情请参照下方博客:博客地址本人亲测有效,不需要安装其他程序,复制好脚本,直接执行即可_centos7 redash 安装

【Python】科研代码学习:十 evaluate (metrics,Evaluator)_python evaluate-程序员宅基地

文章浏览阅读1k次,点赞21次,收藏15次。【代码】【Python】科研代码学习:十 evaluate (metrics,Evaluator)_python evaluate

Tcp如何解决丢包和乱序的问题_tcp乱序的解决办法-程序员宅基地

文章浏览阅读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、可能是跟鸿蒙系统本身有关,因为原..._鸿蒙系统状态网络不可用

随便推点

C语言函数指针数组_c语言 定义函数指针数组-程序员宅基地

C语言函数指针数组的使用方法非常重要,它可以存储多个函数指针,实现灵活的函数调用。通过函数指针数组,我们可以在运行时根据需求选择调用不同的函数。这种技术在编写大型程序时尤为有用。

【并查集】 个人心得&&kuangbin带你飞并查集专题全题解_nefu_qky并查集-程序员宅基地

文章浏览阅读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并查集

JAVA兴趣小组申请理由_关于参与兴趣小组申请书范文-程序员宅基地

文章浏览阅读1.5k次。如何参加兴趣小组呢,那就要写一份申请书,这里给大家分享一些关于参与兴趣小组申请书范文,方便大家学习。参与兴趣小组申请书1时光流逝,转眼又一个学期过去了本学年的美术兴趣小组活动也得以告终,美术兴趣小组活动总结。本着贯彻国家教育方针,实施素质教育的原则,使学生掌握知识技能,发展智力,形成个性品德的目的,使我们的美术兴趣小组顺利开放。其主要目标就是对学生进行审美教育,培养学生健康、正确的审美观念,具有高..._加入兴趣小组的理由

JAVA安全客户端连接到Hbase_hbaseconfig.set-程序员宅基地

文章浏览阅读6.1k次。通过java API 连接到基于kerberos安全验证的Hbase_hbaseconfig.set

java 监听oracle_关于Oracle用java实时监听oracle对表的DML操作【技术贴】-程序员宅基地

文章浏览阅读615次。该楼层疑似违规已被系统折叠隐藏此楼查看此楼直接贴代码(这是在网上找的然后改的复制可以运行):public static void main(String[] args) throws SQLException {OracleDataSource dataSource = new OracleDataSource();dataSource.setUser("...");dataSource.set..._java实时监听oracle表

Unity基础知识学习四,UI框架设计_游戏ui框架设计-程序员宅基地

文章浏览阅读4.1k次,点赞2次,收藏36次。1.什么是UI框架2.为什么要有UI框架3.如何使用UI框架,使用UI框架的不同方案比较4.UI框架源码实现5.UI框架关键点,重要节点,疑难杂症场景_游戏ui框架设计