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

智能推荐

C语言函数递归调用-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏22次。C语言函数递归调用_c语言函数递归调用

明日方舟抽卡模拟器wiki_明日方舟bilibili服-明日方舟bilibili服下载-程序员宅基地

文章浏览阅读410次。明日方舟bilibili服是一款天灾驾到战斗热血的创新二次元废土风塔防手游,精妙的二次元纸片人设计,为宅友们源源不断更新超多的纸片人老婆老公们,玩家将扮演废土正义一方“罗德岛”中的指挥官,与你身边的感染者们并肩作战。与同类塔防手游与众不同的几点,首先你可以在这抽卡轻松获得稀有,同时也可以在战斗体系和敌军走位机制看到不同。明日方舟bilibili服设定:1、起因不明并四处肆虐的天灾,席卷过的土地上出..._明日方舟抽卡模拟器

基于图像的目标检测与定位方法概述_图像定位算法-程序员宅基地

文章浏览阅读1.4w次,点赞15次,收藏97次。目录1. 目标检测与定位概念2. 目标检测与定位方法2.1 传统目标检测流程2.2 two-stage检测算法2.2.1 R-CNN2.2.2 two-stage其他算法2.2.2.1 Spatial Pyramid Pooling(空间金字塔池化)2.2.2.2 Fast-RCNN2.2.2.3 P11 Faster RCNN2.3 One-Stage2.3.1 YOLO2.3.2 SSD参考本文简单介绍基于图像的目标检测与定位相关概念,R-CNN和YOLO等算法基本思想。本文为学习笔记,参考了许多优_图像定位算法

数字图像处理Matlab-小波变换在图像处理中的应用(附代码)_小波分解与重构在图像处理的应用-程序员宅基地

文章浏览阅读9.8k次,点赞40次,收藏131次。目录1.Objectives:2.Experiment Content:3.Experiment Principle:4.Experiment Steps Result and Conlusion:1、了解各种小波性质2、观察各种小波函数及其尺度函数3、获得小波滤波器4、二维小波变换函数与重构函数5、查看及处理小波系数6、用小波变换进行图像压缩与边缘提取【附录】实现代码1.Objectives:..._小波分解与重构在图像处理的应用

如何把海思 Hi3556、Hi3559当ipc芯片使用_hi3556的固件可以刷hi3559吗-程序员宅基地

文章浏览阅读8k次。解决思路:1、网络方面:这两颗料没有网口,那么给linux系统端移植一颗usb转网卡的芯片即可,价格一两块RMB,可行。2、去掉middleware和reference层,直接在ndk层进行操作;3、如果对图像有要求,可以在相关接口扩展指令,从而达到间接使用mpi接口的目的;..._hi3556的固件可以刷hi3559吗

创龙TI OMAP-L138(定点/浮点DSP C674x+ARM9)RJ45以太网口_am1808 代换-程序员宅基地

文章浏览阅读466次。CPUOMAP-L138、TMS320C6748、AM1808三款CPU管脚兼容,外设资源基本相同。OMAP-L138TI公司的达芬奇架构嵌入式应用处理器开始使用DSP与ARM结合的非对称多核结构,OMAP-L138就是其中的一款低功耗双核嵌入式处理器。OMAP-L138双核架构兼具DSP的高数字信号处理性能和精简指令计算机(RISC)技术的优点,双核均是32位处理器。以下是OMAP-L138 CPU的资源框图:RJ45以太网口开发板采用了SMSC的LAN8710A网卡芯片,它可以自_am1808 代换

随便推点

Mysql递归调用,报错:Subquery returns more than 1 row-程序员宅基地

文章浏览阅读2.1k次。Mysql递归调用,报错:Subquery returns more than 1 row_subquery returns more than 1 row

割线定理-程序员宅基地

文章浏览阅读3.2k次。文字表达:从圆外一点引圆的两条割线,这一点到每条割线与圆交点的距离的积相等。数学语言:从圆外一点L引两条割线与圆分别交于A.B.C.D 则有 LA·LB=LC·LD=LT²。几何语言:∵割线LDC和LBA交于圆O于ABCD点∴LA·LB=LC·LD=LT²证明过程:证1:已知:如图直线ABP和CDP是自点P引的⊙O的两条割线求证:PA·PB=PC·PD证明:连接AD、BC∵∠A和∠C都对弧BD∴由圆周角定理,得 ∠DAP=∠BCP又∵∠P=∠P∴△ADP._割线定理

ThinkPHP3.2.3-文章管理系统-附带源码地址_thinkphp3.2.3 源码下载-程序员宅基地

文章浏览阅读6.1k次,点赞3次,收藏7次。一.前言本项目为文章管理系统,系统相对简单。但代码比较规范,适合作为第一个thinkphp项目,有需要的朋友可以看看。Thinkphp版本为3.2.3。涉及到的知识:验证码,文件上传、登录、自动完成、自动验证等。二.简介1.本系统有前台、后台两个模块。2.后台模块功能:栏目管理、文章管理、管理员信息修改。实现了需登录才可进入系统。3.前台_thinkphp3.2.3 源码下载

SpringMVC 中 Controller 是单例还是多例?如何保证并发安全?-程序员宅基地

文章浏览阅读1.4k次。点击上方“Java精选”,选择“设为星标”别问别人为什么,多问自己凭什么!下方有惊喜留言必回,有问必答!每一天进步一点点,是成功的开始...单例模式(Singleton)是程序设计中一种非常重要的设计模式,设计模式也是Java面试重点考察的一个方面。面试经常会问到的一个问题是:SpringMVC中的Controller是单例还是多例,很多同学可能会想当然认为Control..._controller是多线程的吗?如何保证线程安全

JS图片灯箱(lightBox)效果基本原理和demo-程序员宅基地

文章浏览阅读517次。 到年底了,项目不怎么忙,所以有空特地研究了下KISSY中源码JS灯箱效果,感觉代码比较简单,所以就按照他们的思路依赖于Jquery框架也封装了一个,特地分享给大家,以前经常看到网上很多这样的插件,感觉很多人很牛逼的样子,这样的效果也能做出来,碰巧今天自己也能研究出来一个,代码也不多,就300多行代码,嘿嘿!如果写的不够好,或者还不够的,希望大家多多指教!或者多多发表意见,那些需要值得..._$.lightbox

linux shell脚本字符串连接符,学习Linux shell脚本中连接字符串的方法-程序员宅基地

文章浏览阅读816次。这篇文章主要介绍了Linux shell脚本中连接字符串的方法,如果想要在变量后面添加一个字符,可以用一下方法:代码如下:$value1=home$value2=${value1}"="echo $value2把要添加的字符串变量添加{},并且需要把$放到外面。这样输出的结果是:home=,也就是说连接成功。又如代码如下:[root@localhost sh]# var1=http://www.3..._shell字符串连接符