【swoole.1.03】异步客户端,心跳检测和心跳包

一、异步客户端

在上一篇中我体验了一下swoole的基础客户端和服务端间的交互,其中客户端是最基础的同步客户端,即客户端的一整套连接到服务器->向服务器发送数据->从服务器接收数据->关闭连接行为都是同步阻塞执行的,这样不就失去了socket开发的意义了吗?今天我就体验一下使用swoole创建异步客户端并进行交互。

注意:由于我本地的swoole版本为4.2.13,而从4.4.8版本开始,移除了对异步回调的支持,迁移至ext-async扩展中,独立为Swoole\Async\Client类,所以新版的用户可以直接查看手册:swoole手册-异步非阻塞客户端,回调式编程的风格。,用法与服务端一致,都是创建服务端后注册回调,这里只演示旧版代码。旧版用户请看文档创建异步TCP客户端

异步客户端和同步客户端是在构造对象时用一个参数区分的

    /**
     * swoole_client构造函数
     *
     * @param int $sock_type 指定socket的类型,支持TCP/UDP、TCP6/UDP64种
     * @param int $sync_type SWOOLE_SOCK_SYNC/SWOOLE_SOCK_ASYNC  同步/异步
     * @param string $connectionKey 链接的编号,用于长连接复用
     */
    public function __construct($sock_type, $sync_type = SWOOLE_SOCK_SYNC, $connectionKey = '')
    {
    }

可以看到第二个参数int $sync_type SWOOLE_SOCK_SYNC/SWOOLE_SOCK_ASYNC 同步/异步,默认为SWOOLE_SOCK_SYNC,我们只需要将其改为SWOOLE_SOCK_ASYNC即可

异步客户端支持4个回调,同时在连接时必须注册这4个回调才能连接成功,它们分别是

  1. connect 连接回调事件
  2. receive 收到消息的回调事件
  3. error 错误时的回调事件
  4. close 关闭时的回调事件

服务端:

<?php
/**
 * User: dl
 * Date: 2019/10/30 0030
 * Time: 17:22
 */

//  创建server对象,监听所有ip,9001端口
$server = new \Swoole\Server('0.0.0.0', 9001);

//  修改配置
$server->set([
    'worker_num' => 2
]);

//  监听连接进入事件
$server->on('connect', function (\Swoole\Server $server, int $fd, int $reactorId) {
    echo "触发连接回调" . PHP_EOL;
    $message = '欢迎,你的fd是:' . $fd;
    echo "我发送了【{$message}】" . PHP_EOL;
    $server->send($fd, $message);
});

//  监听数据接收事件
$server->on('receive', function () {
    echo "触发数据接收回调" . PHP_EOL;
});

//  监听连接关闭事件
$server->on('close', function () {
    echo "触发关闭回调" . PHP_EOL;
});

//  启动服务器
$server->start();

客户端:

<?php
//  创建客户端
$client = new \Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

//  连接服务器事件
$client->on('connect', function (\Swoole\Client $client) {
    $client->send('我来了');
});

//  接收消息事件
$client->on('receive', function (\Swoole\Client $client, $data) {
    echo $data . PHP_EOL;
});

//  出错事件
$client->on('error', function (\Swoole\Client $client) {
    echo '出错了' . PHP_EOL;
});

//  关闭连接事件
$client->on('close', function (\Swoole\Client $client) {
    echo '关闭了' . PHP_EOL;
});

//  连接到服务器
$client->connect('127.0.0.1', 9001);

//  关闭连接
$client->close();

执行结果:

服务端:

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php server.php 
触发连接回调
我发送了【欢迎,你的fd是:1】
[2019-10-31 16:08:39 *26525.1]	NOTICE	swFactoryProcess_finish (ERROR 1004): send 24 byte failed, because connection[fd=1] is closed.
触发关闭回调

客户端:

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php client.php 
PHP Warning:  Swoole\Client::close(): client socket is closed. in Unknown on line 0

可以看到客户端并没有和上一篇一样,等待服务端响应并recv后关闭,而是在注册完事件,连接后的瞬间就关闭了,所以服务端爆出了一个提示,发送数据失败,fd=1的socket连接已经关闭了。

那么去掉close后再观察一下

客户端代码:

<?php
//  创建客户端
$client = new \Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

//  连接服务器事件
$client->on('connect', function (\Swoole\Client $client) {
    $client->send('我来了');
});

//  接收消息事件
$client->on('receive', function (\Swoole\Client $client, $data) {
    echo $data . PHP_EOL;
});

//  出错事件
$client->on('error', function (\Swoole\Client $client) {
    echo '出错了' . PHP_EOL;
});

//  关闭连接事件
$client->on('close', function (\Swoole\Client $client) {
    echo '关闭了' . PHP_EOL;
});

//  连接到服务器
$client->connect('127.0.0.1', 9001);

//  关闭连接
//$client->close();

服务端:

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php server.php 
触发连接回调
我发送了【欢迎,你的fd是:1】
触发数据接收回调

客户端:

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php client.php 
欢迎,你的fd是:1

可以看到现在正常触发了receive回调,从这里我们就能看出同步和异步的区别了。

同步和异步最大的区别在于结果接受的处理上。 同步:当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行。 异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。

对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。

作为一个PHP程序员,web开发中随处可见普通的同步请求和异步的ajax请求,同步和异步的区别和各自的有点就不再赘述了。这里给各位程序幼儿员找了几篇其他博主的博客,有兴趣可以自行阅读。 [基础]同步消息和异步消息传递的区别? 同步和异步的区别

二、心跳是什么

心跳,心跳检测,在做socket开发前听到比较多的地方应该就是服务器的集群了,毕竟作为一个PHPER队服务器还是要有一点了解的。那么在多台服务器集群的时候如何保证服务器高可用,保证分发到的服务器没有宕机呢?有一种方法叫做heartbeat,heartbeat的原理很简单,就是通过每一台集群内的服务器不停发送数据包告诉主服务器我还在工作。

那么回到swoole,为什么socket开发需要用到心跳机制?

  1. socket连接的套接字标示fd是有限的,通过心跳检测可以剔除已下线的客户端,空余出新的fd用来分配。
  2. 通过心跳包检测判断客户端是否在线来减少通知的发放,减少服务器使用的资源。
  3. 防止由于客户端长期没有操作导致断开连接。

swoole如何配置心跳处理

swoole的配置中已封装好心跳相关的配置, heartbeat_check_interval 启用心跳检测 heartbeat_idle_time 连接最大允许空闲的时间

heartbeat_check_interval

启用心跳检测,此选项表示每隔多久轮循一次,单位为秒。如 heartbeat_check_interval => 60,表示每60秒,遍历所有连接,如果该连接在120秒内(heartbeat_idle_time未设置时默认为interval的两倍),没有向服务器发送任何数据,此连接将被强制关闭。若未配置,则不会启用心跳, 该配置默认关闭。

Server并不会主动向客户端发送心跳包,而是被动等待客户端发送心跳。服务器端的heartbeat_check仅仅是检测连接上一次发送数据的时间,如果超过限制,将切断连接。

被心跳检测切断的连接依然会触发onClose事件回调

heartbeat_check仅支持TCP连接

heartbeat_idle_time

heartbeat_check_interval配合使用。表示连接最大允许空闲的时间。如

array(
    'heartbeat_idle_time' => 600,
    'heartbeat_check_interval' => 60,
);

表示每60秒遍历一次,一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭

启用heartbeat_idle_time后,服务器并不会主动向客户端发送数据包

如果只设置了heartbeat_idle_time未设置heartbeat_check_interval底层将不会创建心跳检测线程,PHP代码中可以调用heartbeat方法手工处理超时的连接

我在服务端上加上的配置

$server->set([
    'worker_num' => 2,
    'heartbeat_idle_time' => 8,
    'heartbeat_check_interval' => 3
]);

在客户端连接8秒后,因为没有任何新的消息进入,自动触发了close事件(服务端主动断开了这个客户端的链接)

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php server.php 
触发连接回调
我发送了【欢迎,你的fd是:1】
触发关闭回调

保持心跳和定时器

要保持心跳其实非常简单,只要在允许空闲时间内发送任意大小的数据给服务端就行,但是作为socket开发,我们用传统的while(true){}又显得很蠢,其实swoole扩展已经给我们提供了各种定时器及定时器类:Timer

作为一个合格的PHPER,前端技能不能少,和js一样,swoole提供了一次性定时器 Timer::after 和间隔循环定时器 Timer::tick

我们只要使用循环定时器就可以完成心跳包的模拟啦!在连接服务端回调中加入定时器

//  连接服务器事件
$client->on('connect', function (\Swoole\Client $client) {
    $client->send('我来了');

    //  心跳
    \Swoole\Timer::tick(3000, function () use ($client) {
        $client->send(1);
    });
});

就可以啦!尝试一下!

服务端

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php server.php
触发连接回调
我发送了【欢迎,你的fd是:1】
触发数据接收回调,已接收:我来了
触发数据接收回调,已接收:1
触发数据接收回调,已接收:1
触发数据接收回调,已接收:1
触发数据接收回调,已接收:1

服务端

[root@iZbp1acp86oa3ixxw4n1dpZ 1.03]# php client.php
欢迎,你的fd是:1
已发送心跳
已发送心跳
已发送心跳
已发送心跳

这样客户端就不会在存活时被心跳检测强制断开了

附录

源码:点击下载

swoole手册-异步非阻塞客户端,回调式编程的风格。

创建异步TCP客户端

[基础]同步消息和异步消息传递的区别?

同步和异步的区别

heartbeat

heartbeat_check_interval 启用心跳检测

heartbeat_idle_time 连接最大允许空闲的时间

Timer

Timer::tick

Timer::after

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论