长连接说简单一点就是不会断的连接 😛 ,可以使用心跳包进行维持
心跳是什么?
顾名思义就是心脏的跳动,可以用来判断一个事物的生和死,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连接非常不稳定,所以需要一套机制来保证服务器和客户端之间连接的有效性,所以就有了心跳机制
什么是心跳机制?
心跳机制就是业务层来提供一个连接是否存活的一个方法,让系统能判定一个连接是否失效
一般有两种实现方式:
- 客户端定时发送一个心跳包,告诉服务器我还活着,服务器定时检测所有客户端列表,看他们最后一个心跳包的时间是否过长,如果过长,则认为已无心跳,判定为死连接,主动关闭这个连接
- 服务器定时询问所有的客户端,你们还活着么?如果活着,给我个回馈,没得到回馈的客户端,主动关闭这个连接
两种心跳方案有什么区别?
第一种方案,对服务器和网络的压力更小,而且更具有灵活性,但需要客户端配合定时发送心跳包
第二种方案,对服务器和网络压力更大,不建议使用
在Swoole中如何实现?
Swoole扩展本身内置了这种机制,开发者只需要配置一个参数即可启用。Swoole在每次收到客户端数据会记录一个时间戳,当客户端在一定时间内未向服务器端发送数据,那服务器会自动切断连接
使用方式
在Server启动时增加两个参数
$serv->set(array(
'heartbeat_check_interval' => 5,
'heartbeat_idle_time' => 10,
));
设置了这两个参数后,Swoole底层将会创建心跳检测线程,通过定时轮询所有的连接,来判断连接的生死,所以Swoole的心跳不会堵塞任何业务逻辑
上面的设置就是每5秒侦测一次心跳,一个TCP连接如果在10秒内未向服务器端发送数据,将会被切断
配置建议
建议heartbeat_idle_time
为heartbeat_check_interval
的两倍多一点
这个两倍是为了进行容错,允许丢一个包,而多一点是考虑到网络的延时
你可以跟据实际的业务来调整这个容错率(允许丢几个包)
在客户端发送心跳包
使用定时器定时向服务端发送心跳
Swoole\Timer::tick(3000, function () use ($client) {
$data = "heartbeat";
$client->send($data);
});
服务端和客户端示例代码:https://github.com/sy-records/learn-swoole/tree/master/heartbeat
路过学习~