Swoole长连接,心跳包Heartbeat检测
首先:长连接都是很消耗资源的。所以,需要有个机制来检测客户端是否还活着 Or 存在,不存在就断开连接,减少资源消耗。
众所周知,Tcp连接需要3次握手跟4次挥手。不管是服务器主动发起断开连接,还是客户端发起断开连接,都要经历完整的四次挥手阶段。最后由系统回收客户端的文件描述符fd(后面看代码就能看出fd标识符文件是啥)。
为什么要回收描述符呢
还是那句话,减少资源消耗。在UNIX哲学中一切皆是文件,文件描述符对于操作系统而言是有限的资源,必须重复利用,所以必须要回收。
回到话题。心跳机制
心跳机制
心跳机制有两种实现方式:
1.轮询
用简单的话来说。由服务器定时去向所有的客户端发送消息,询问客户端是否还需要保持连接,是否在线。服务器定时询问的方式,对服务器和网络的压力更大
2.心跳包
最终得到的结果是与轮询一样的但是实现的方式有差别,心跳包是由客户端主动发送一个心跳包,告知服务器连接仍旧还活着。服务端记录所有的客户端,如果超过一定时间,客户端没有来发送数据,或者说更新自己的状态,则视为死链接,从而断开连接。客户端定时发送心跳包的方式,对服务器和网络的压力更小,更加灵活。
Swoole 实现心跳机制
有两个参数在创建Tcp服务的时候设置就可以
$serv->set(array(
'heartbeat_check_interval' => 5,
'heartbeat_idle_time' => 10,
));
其中
heartbeat_check_interval
设置服务器定时检测在线列表的时间间隔
heartbeat_idle_time
设置连接最大的空闲时间,如果最后一个心跳包的时间与当前时间只差超过设定值则认为连接失效。
建议heartbeat_idle_time比heartbeat_check_interval的值多两倍多,两倍是为了进行容错允许丢包,多一点儿是考虑到网络延时的情况,这个可以根据实际的业务情况调整容错率。
使用Swoole 心跳实例;
一、创建Tcp服务。
<?php
class Server
{
private $server;
public function __construct($host, $port, $config)
{
//创建服务器
$this->server = new swoole_server($host, $port);
//设置运行时参数
$this->server->set($config);
//设置监听
$this->server->on("Start", [$this, "onStart"]);
$this->server->on("Connect", [$this, "onConnect"]);
$this->server->on("Receive", [$this, "onReceive"]);
$this->server->on("Close", [$this, "onClose"]);
//开启服务器
$this->server->start();
}
public function onStart($server)
{
echo "[start] master {$server->master_pid} manager {$server->manager_pid}".PHP_EOL;
}
public function onConnect($server, $fd, $reactor_id)
{
echo "[connect] reactor {$reactor_id} worker {$server->worker_pid} client {$fd}".PHP_EOL;
}
public function onReceive($server, $fd, $reactor_id, $data)
{
echo "[receive] reactor {$reactor_id} worker {$server->worker_pid} client {$fd}: {$data}".PHP_EOL;
$server->send($fd, $data);
}
public function onClose($server, $fd)
{
echo "[close] client {$fd} close".PHP_EOL;
}
}
$config = [];
$config["worker_num"] = 8;
$config["daemonize"] = 0;
$config["max_request"] = 1000;
$config["dispatch_mode"] = 2;
$config["debug_mode"] = 1;
$config["log_file"] = "/swoole.log";
$config["heartbeat_check_interval"] = 5;
$config["heartbeat_idle_time"] = 10;
$host = "0.0.0.0";
$port = 9000;
$server = new Server($host, $port, $config);
二、创建异步Tcp客户端
<?php
use Swoole\Async\Client as AsyncClinet;
class Client
{
private $client;
public function __construct($host, $port)
{
$this->client = new AsyncClinet(SWOOLE_SOCK_TCP);
$this->client->on("Connect", [$this, "onConnect"]);
$this->client->on("Receive", [$this, "onReceive"]);
$this->client->on("Close", [$this, "onClose"]);
$this->client->on("Error", [$this, "onError"]);
if(!$fp = $this->client->connect($host, $port)){
echo "error {$fp->errCode} {$fp->errMsg}";
return;
}
}
public function onConnect($client)
{
fwrite(STDOUT, "send: ");
swoole_event_add(STDIN, function(){
fwrite(STDOUT, "send: ");
$this->client->send(trim(fgets(STDIN)));
});
}
public function onReceive($client, $data)
{
echo $data.PHP_EOL;
}
public function onClose($client)
{
echo "close".PHP_EOL;
}
public function onError($client)
{
echo "error {$client->errCode} {$client->errMsg}".PHP_EOL;
}
}
$client = new Client("127.0.0.1", 9000);
重要提示 :
从4.3版本开始需要额外安装swoole-async扩展才能使用异步模块
不要直接拉取 master 代码进行编译,请使用和 swoole 版本对应的 ext-async 的 release 版本
自行安装swoole-async http://wiki.swoole.com/wiki/page/p-async.html 有详细的介绍。不懂可以留言
运行实例:
1.运行写好的tcp_server;
[xxxxx]# php tcp_server.php
[start] master 7357 manager 7358
2.运行写好的tcp_client;
[xxxxx]# php tcp_async_client.php
send: 123546
send: 123546
3.查看服务端
[xxxxx]# php tcp_server.php
[start] master 7357 manager 7358
[connect] reactor 0 worker 7361 client 1
[receive] reactor 0 worker 7361 client 1: 123546
4过段时间再查看客户端,发现close。
[xxxxxx]# php tcp_server.php
[start] master 7357 manager 7358
[connect] reactor 0 worker 7361 client 1
[receive] reactor 0 worker 7361 client 1: 123546
[close] client 1 close
这就是Swoole 的心跳检测。我们并没用主动断开Tcp_client 的服务。而是服务端在一定时间内,没检测到来自 1(这个1 就是前面说的描述表示fd)的数据。
大家可以尝试持续发送数据,然后再停一段时间。就可以发现,持续发送数据期间,是不会断开连接的,只有停止一段时间后,服务器会主动断开连接。