Swoole:心跳检测

前言:

心跳:心脏还在跳动,说明还有生命迹象,还活着,还活着就表示还可以继续工作,生命不止,工作不息。

WHY

为什么需要心跳检测?这个小孩没娘,说来话长,long long ago ,TCP协议的诞生,惊天地泣鬼神,改变了世界。

建立连接:三次握手

Swooleï¼å¿è·³æ£æµ

断开连接:四次挥手 ‍♂️

Swooleï¼å¿è·³æ£æµ

趣解:

寓言两则:(client、server的精彩对话)。此故事纯属虚构,如有雷同,不好意思。

人物一:小C(client 客户端)

人物二:大S (server 服务端)

场景一:初识(建立连接 ——三次握手)

小C:很高兴认识你,我想和你处对象。

大S:看你挺有诚意的,我同意

小C:收到对方的回信,欣喜若狂,赶紧再说:“你等着我啊,马上咱们就*****(美好的生活)”;

(第一次握手:客户端发送信号,我准备发送数据,客户端进入准备发送状态)

(第二次握手:服务端收到信号,并给客户端回信,服务端进入准备接收状态)

(第三次握手:客户端收到回信,进入已连接状态,并给服务端确认,服务端收到,两端都进入建立连接状态)

场景二:依依惜别 (断开连接——四次挥手)

小C:我不想和你说话了,

大S:你是在开玩笑吧,

大S:(看到小C认真的面容,知道这不是玩笑),好吧

小C:你同意就好,再见。(看这大S伤心而去,驻足一段时间,自己也离开了)

(第一次挥手:客户端发出关闭请求,此时不在发送数据。)

(第二次挥手:服务端收到请求并确认,服务端进入等待关闭状态——把自己想说的话,说完)

(第三次挥手:服务端向客户端发送确认关闭信号,此时不再发送数据,进入最后确认关闭状态)

(第四次挥手:客户端收到关闭请求,向服务端恢复信息,服务端关闭。客户端会在稍后关闭)

详情查看:https://blog.csdn.net/qq_37837134/article/details/79738329

上面简单了解了TCP协议,那么有没有这样一种场景呢:只建立连接,而不断开连接

答案是肯定的,在复杂的网络环境中,这种情况必然存在。例如:夏天用电高峰期负载高,断电了;我的家里刚通网,还是不很好,时常在高潮的时候莫名其妙的断网……

总之,无缘无故的不辞而别,总是存在的,没有那么完美的爱情。

what

心跳机制:客户端定时发送 ping,服务端回复 pong。

ping、 pong 无特殊意义,就是简单的通讯传输,朋友之间常联络,才不会生疏。时间久了,服务端就要把你踢了,人的精力总是有限的,有的时候,整理自己的心情,会删除一些无用的通讯录。

服务端的资源也是有限的,那么就要把没用的fd 收回,重复利用。

fd 是什么?

fd (file descriptor) 文件描述符

一切皆文件,fd 就相当于索引,拿着这个标示去进行一系列操作。

swoole 中

  • $fd是TCP客户端连接的标识符,在Server实例中是唯一的,在多个进程内不会重复
  • fd 是一个自增数字,范围是1 ~ 1600万,fd超过1600万后会自动从1开始进行复用
  • $fd是复用的,当连接关闭后fd会被新进入的连接复用
  • 正在维持的TCP连接fd不会被复用

不要担心,同一台服务1600万的fd 不够自己用,因为服务器承受不了1600万的连接,如果真有那么多连接,肯定也不是单机了。

How

swoole 实际上已经实现了心跳检测机制,咱们只需要去开启配置就行了。如此方便,不得不赞,峰哥威武!

Swooleï¼å¿è·³æ£æµ

heartbeat_check_interval

心跳检测 每隔多少秒,遍历一遍所有的连接

heartbeat_idle_time

心跳检测 最大闲置时间,超时触发close并关闭  默认为heartbeat_check_interval的2倍,两倍是容错机制,多一点是网络延迟的弥补

官方文档:https://wiki.swoole.com/wiki/page/283.html

当然,心跳机制也可以自己实现,定期轮询fd,是否在线,记录最近回话时间,剔除超时的连接。

伪代码

webSocket Server:

<?php
/**
 * 心跳检测机制 测试
 * Created by PhpStorm.
 * User: 奔跑吧笨笨
 * Date: 2019/8/1
 * Time: 3:04 PM
 */

include_once './class/UsersBind.php';

//创建websocket服务器对象,监听0.0.0.0:9876端口
$ws = new swoole_websocket_server("0.0.0.0", 9876);

$ws->set([
    'heartbeat_check_interval' => 30,       //心跳检测 每隔多少秒,遍历一遍所有的连接
    'heartbeat_idle_time' => 65,           //心跳检测 最大闲置时间,超时触发close并关闭  默认为heartbeat_check_interval的2倍,两倍是容错机制,多一点是网络延迟的弥补
]);

//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->get, $request->server);
    $ws->push($request->fd, "hello, welcome\n");

    //一、建立连接 uid、fd 关系绑定
    $bindObj = new UsersBind();
    $bindObj->userIdBind(1,$request->fd);
    $bindObj->fdBind(1,$request->fd);
});

//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
    //二、连接关闭 通过uid,获取fd
    $bindObj = new UsersBind();
    $fd = $bindObj->getBindFd(2);

    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});

//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
    $close_str = "client-{$fd} is closed\n";
    echo $close_str;
    file_put_contents('./a.log',$close_str,FILE_APPEND);

    //三、连接关闭 uid、fd 关系解除
    $bindObj = new UsersBind();
    $bindObj->unbindFd(1,$fd);
});

$ws->start();

客户端 client js:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws连接</title>
</head>
<body>

</body>
<script>
    var wsServer = 'ws://127.0.0.1:9876';
    var websocket = new WebSocket(wsServer);
    websocket.onopen = function (evt) {
        console.log("Connected to WebSocket server.");
    };

    websocket.onclose = function (evt) {
        console.log("Disconnected");
    };

    websocket.onmessage = function (evt) {
        console.log('Retrieved data from server: ' + evt.data);
        //websocket.send('Lalal');
    };

    websocket.onerror = function (evt, e) {
        console.log('Error occured: ' + evt.data);
    };
</script>
</html>

fd 结合业务user id 用户关系映射:

场景:

  • 多台服务器
  • 同一用户多个客户端可同时登录,消息共享
<?php

/**
 * Created by PhpStorm.
 * User: runBaby
 * Date: 2019/8/3
 * Time: 5:55 PM
 */
class UsersBind
{


    private $redisObj;
    private $fieldPrefix;
    private $serverNode;
    private $redisTable;

    private $clientNode = ['APP', 'PC'];

    const REDIS_HOST = '127.0.0.1';
    const REDIS_PORT = 6379;
    const REDIS_TIMEOUT = 2;

    public function __construct($redisTable = 'ws', $fieldPrefix = 'bind', $serverNode = 0, $client = 0)
    {
        $this->redisObj = new Redis();
        $this->redisObj->connect(self::REDIS_HOST, self::REDIS_PORT, self::REDIS_TIMEOUT);

        $this->redisTable = $redisTable;
        $this->serverNode = $serverNode;
        $this->clientNode = $this->clientNode[$client];
        $this->fieldPrefix = $fieldPrefix;

    }


    /**
     * Explain: set field prefix
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:15 PM
     * @return string
     */
    private function setFieldPrefix($type = 0)
    {
        $keyPrefix = $this->fieldPrefix . ':server:' . $this->serverNode . ':client:' . $this->clientNode . ':';
        if ($type === 1) {
            $keyPrefix .= 'fd:';
        } else {
            $keyPrefix .= 'uid:';
        }


        return $keyPrefix;
    }


    /**
     * Explain: redis key
     * @param $uid
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:26 PM
     * @return string
     */
    private function setRedisField($id, $type = 0)
    {
        $fieldPrefix = $this->setFieldPrefix($type);
        $key = $fieldPrefix . $id;


        return $key;
    }


    /**
     * Explain: user id bind fd
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:23 PM
     * @return int
     */
    public function userIdBind($uid, $fd)
    {
        $field = $this->setRedisField($uid);
        $result = $this->redisObj->hSet($this->redisTable, $field, $fd);

        return $result;
    }


    /**
     * Explain: fd bind user id
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/4
     * Time: 9:08 PM
     * @return int
     */
    public function fdBind($uid, $fd)
    {
        $field = $this->setRedisField($fd, 1);
        $result = $this->redisObj->hSet($this->redisTable, $field, $uid);


        return $result;
    }


    /**
     * Explain: Two-way binding
     * @param $uid
     * @param $fd
     * User: runBaby
     * Date: 2019/8/4
     * Time: 9:32 PM
     * @return bool
     */
    public function setBindId($uid, $fd)
    {
        $result_uid = $this->userIdBind($uid, $fd);
        $result_fd = $this->fdBind($uid, $fd);


        if($result_uid && $result_fd) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * Explain: get bind fd
     * @param $uid
     * User:runBaby
     * Date: 2019/8/3
     * Time: 6:28 PM
     * @return string
     */
    public function getBindFd($uid)
    {
        $field = $this->setRedisField($uid);
        $data = $this->redisObj->hGet($this->redisTable, $field);


        return $data;
    }


    /**
     * Explain: get bind user id
     * @param $fd
     * User:runBaby
     * Date: 2019/8/3
     * Time: 6:28 PM
     * @return string
     */
    public function getBindUserId($fd)
    {
        $field = $this->setRedisField($fd, 1);
        $data = $this->redisObj->hGet($this->redisTable, $field);


        return $data;
    }


    /**
     * Explain: unbinds userId and  fd
     * @param $uid
     * User: runBaby
     * Date: 2019/8/3
     * Time: 6:38 PM
     * @return bool
     */
    public function unbindFd($uid, $fd)
    {
        //unbind userId
        $field_uid = $this->setRedisField($uid);
        $result_uid = $this->redisObj->hDel($this->redisTable, $field_uid);
        //unbind fd
        $field_fd = $this->setRedisField($fd, 1);
        $result_fd = $this->redisObj->hDel($this->redisTable, $field_fd);


        if($result_uid && $result_fd) {
            return true;
        } else {
            return false;
        }
    }


}

 

 

我为人人,人人为我,美美与共,天下大同。

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值