Swoole中的长连接和心跳包

长连接说简单一点就是不会断的连接 😛 ,可以使用心跳包进行维持

心跳是什么?

顾名思义就是心脏的跳动,可以用来判断一个事物的生和死,Swoole 中的心跳是指用来判断一个连接是正常还是断开的

fd 是什么?

fd 学名是文件描述符(file descriptor),Swoole Server 中$fd 是 TCP 客户端连接的标识符,在 Server 实例中是唯一的,在多个进程内不会重复

fd 是一个自增数字,范围是 1 ~ 1600 万,fd 超过 1600 万后会自动从 1 开始进行复用

当连接关闭后 fd 会被新进入的连接复用,正在维持的 TCP 连接 fd 不会被复用

可以使用Server->getClientInfo函数来获取连接的信息,看一下我们可以通过 fd 获取到什么信息

var_dump($serv->getClientInfo($fd));

打印后的数据为:

array(10) {
  ["server_port"]=> // 服务端端口
  int(9501)
  ["server_fd"]=> // fd
  int(4)
  ["socket_fd"]=>
  int(11)
  ["socket_type"]=>
  int(1)
  ["remote_port"]=> // 客户端连接的端口
  int(49152)
  ["remote_ip"]=> // 客户端连接的 IP 地址
  string(9) "127.0.0.1"
  ["reactor_id"]=> // 来自哪个 Reactor 线程
  int(0)
  ["connect_time"]=> // 客户端连接到 Server 的时间,单位秒,由 master 进程设置
  int(1562741559)
  ["last_time"]=> // 最后一次收到数据的时间,单位秒,由 master 进程设置
  int(1562741559)
  ["close_errno"]=> // 连接关闭的错误码,如果连接异常关闭,close_errno 的值是非零
  int(0)
}

为什么要心跳?

当我们要关闭客户端连接时,我们可以在业务层对 fd 发起关闭连接的操作,以 Swoole 为例:

$serv->close($fd);

Swoole 会有 onClose 回调,之前我们也说了连接关闭后 fd 会被新进入的连接复用

正常情况下客户端中断 TCP 连接时,会发送一个 FIN 包,进行 4 次断开握手来通知服务器。但一些异常情况下,如客户端突然断电断网或者网络异常,服务器可能无法得知客户端已断开连接

尤其是移动网络,TCP 连接非常不稳定,所以需要一套机制来保证服务器和客户端之间连接的有效性,所以就有了心跳机制

什么是心跳机制?

心跳机制就是业务层来提供一个连接是否存活的一个方法,让系统能判定一个连接是否失效

一般有两种实现方式:

  1. 客户端定时发送一个心跳包,告诉服务器我还活着,服务器定时检测所有客户端列表,看他们最后一个心跳包的时间是否过长,如果过长,则认为已无心跳,判定为死连接,主动关闭这个连接
  2. 服务器定时询问所有的客户端,你们还活着么?如果活着,给我个回馈,没得到回馈的客户端,主动关闭这个连接

两种心跳方案有什么区别?

第一种方案,对服务器和网络的压力更小,而且更具有灵活性,但需要客户端配合定时发送心跳包

第二种方案,对服务器和网络压力更大,不建议使用

在 Swoole 中如何实现?

Swoole 扩展本身内置了这种机制,开发者只需要配置一个参数即可启用。Swoole 在每次收到客户端数据会记录一个时间戳,当客户端在一定时间内未向服务器端发送数据,那服务器会自动切断连接

使用方式

在 Server 启动时增加两个参数

$serv->set(array(
    'heartbeat_check_interval' => 5,
    'heartbeat_idle_time' => 10,
));

设置了这两个参数后,Swoole 底层将会创建心跳检测线程,通过定时轮询所有的连接,来判断连接的生死,所以 Swoole 的心跳不会堵塞任何业务逻辑

上面的设置就是每 5 秒侦测一次心跳,一个 TCP 连接如果在 10 秒内未向服务器端发送数据,将会被切断

配置建议

建议heartbeat_idle_timeheartbeat_check_interval的两倍多一点

这个两倍是为了进行容错,允许丢一个包,而多一点是考虑到网络的延时

你可以跟据实际的业务来调整这个容错率(允许丢几个包)

在客户端发送心跳包

使用定时器定时向服务端发送心跳

Swoole\Timer::tick(3000, function () use ($client) {
    $data = "heartbeat";
    $client->send($data);
});

服务端和客户端示例代码:https://github.com/sy-records/learn-swoole/tree/master/heartbeat

1 条评论

发表评论

*