首先了解什么是长连接?
须知:服务器建立和关闭连接均是会有资源的消耗的
长连接:其实就是一直连接的方式
短连接:主要是建立一次连接,就没有了 ajax
ajax的特点就是主动向服务器发送请求,获取数据之后处理;那么我们为什么需要长连接呢?
比如:在做即时聊天的时候-》QQ,不管是客户端还是服务端;在用户发送了消息之后就需要能接收到并且又能够接收到服务器发送的消息;
我们可以通过与ajax来实现这个功能, 但是ajax在实现代码的时候因为它的特点只能是主动请求, 因此如果我们需要用ajax来实现的话那么这个时候就不得不让ajax进行轮询;问题如下:
前台不断的通过ajax请求与后台获取数据; 并进行响应和输出;如果说是单台服务器并且只是单个连接, 其实问题不大; 但是如果说链接很多那么服务器就会存在这很大的压力; 在这个过程中服务器会不断的创建连接关闭连接会消耗很多不必要的资源; =》 这个时候短连接就无法满足与我们项目的功能的需要 因此就需要长连接的帮助了;其实还存在一个问题服务端也需要主动发送消息给客户端
心跳检测
在实际的生产环境中,更多的情况是在于用户通过内网的服务端 访问 上线部署的外网服务端;然而在这个过程中用户的服务端口可能因为一些网络方面的因素会造成与外网的服务器断开连接 -》 最典型的例子 “英雄联盟突然掉线了, QQ的登入状态”; 这些在线上的过程都是属于长连接的类型,那么就会存在于这些问题;那么这个时候我们应该如何检测呢??(当然除了断开连接还有一些其他问题,比如超时,阻塞等问题 之后解释)
首先须知: 对于服务端来说信息“基本”是公开的 比如ip地址,状态基本是在运行状态; 那么客户端呢?实际上客户端并不是这么一回事,他的运行状态,ip地址不是固定的;
而对于长连接这种断开的问题;主要的点就在于服务端会保存客户端会话的有效性以及平台上监控所有客户端的网络状况;对于这种功能的实现我们可以通过两种方式实现
1. 轮询机制
2. 心跳机制
轮询:概括来说是服务端定时主动的去与要监控状态的客户端(或者叫其他系统)通信,询问当前的某种状态,客户端返回状态信息,客户端没有返回或返回错误、失效信息、则认为客户端已经宕机,然后服务端自己内部把这个客户端的状态保存下来(宕机或者其他),如果客户端正常,那么返回正常状态,如果客户端宕机或者返回的是定义的失效状态那么当前的客户端状态是能够及时的监控到的,如果客户端宕机之后重启了那么当服务端定时来轮询的时候,还是可以正常的获取返回信息,把其状态重新更新。
心跳:最终得到的结果是与轮询一样的但是实现的方式有差别,心跳不是服务端主动去发信息检测客户端状态,而是在服务端保存下来所有客户端的状态信息,然后等待客户端定时来访问服务端,更新自己的当前状态,如果客户端超过指定的时间没有来更新状态,则认为客户端已经宕机或者其状态异常。
心跳机制与轮询的比较,在我们的应用中,采用的是心跳,这样一是避免服务端的压力,二是灵活好控制,上一篇文章中提到过,我们的外网服务端(服务端)不知道内网服务端(客户端)的地址,有虽然有保存客户端的socket会话,但是客户端宕机会话就失效了。所以只能等着他主动来报告状态。
注意:在客户端向服务端发送的标识我们可以称之为叫心跳包
什么是心跳包呢?
从客户端到服务器这条巨大的链路中会经过无数的路由器,每个路由器都可能会检测多少秒时间内没有数据包,则会自动关闭连接的节能机制。为了让这个可能会出现的节能机制失效,客户端可以设置一个定时器,每隔固定时间发送一个随机字符一 字节的数据包,这种数据包就是心跳包。
swoole中的心跳检测
在swoole中我们可以通过set的方式来设置参数值;
heartbeat_check_interval 设置服务器定时检测在线列表的时间间隔
heartbeat_idle_time 设置连接最大的空闲时间,如果最后-一个心跳包的时间与当前时间只差超过设定值则认为连接 失效。
建议heartbeat_idle_time 比heartbeat_check_interval 的值多两倍多,两倍是为了进行容错允许丢包,多一点儿是考虑到网络延时的情况,这个可以根据实际的业务情况调整容错率。
<?php
//创建Server对象,监听 127.0.0.1:9501端口
$serv = new Swoole\Server("127.0.0.1", 9501);
$serv->set([
// https://wiki.swoole.com/wiki/page/283.html
// https://wiki.swoole.com/wiki/page/284.html
//心跳检测,每三秒检测一次,10秒没活动就断开
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3 //服务器定时检查
]);
//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {
echo "Client ".$fd.": Connect.\n";
});
//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
$serv->send($fd, "Server: ".$data);
});
//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
echo "Client: ".$fd."Close.\n" ;
});
echo "启动swoole tcp server 访问地址 127.0.0.1:9501 \n";
//启动服务器
$serv->start();<?php
//创建Server对象,监听 127.0.0.1:9501端口
$serv = new Swoole\Server("127.0.0.1", 9501);
$serv->set([
// https://wiki.swoole.com/wiki/page/283.html
// https://wiki.swoole.com/wiki/page/284.html
//心跳检测,每三秒检测一次,10秒没活动就断开
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3 //服务器定时检查
]);
//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {
echo "Client ".$fd.": Connect.\n";
});
//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
$serv->send($fd, "Server: ".$data);
});
//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
echo "Client: ".$fd."Close.\n" ;
});
echo "启动swoole tcp server 访问地址 127.0.0.1:9501 \n";
//启动服务器
$serv->start();
客户端
<?php
use Swoole\Async\Client;
$client = new Client(SWOOLE_SOCK_TCP);
$client->on("connect", function(Client $cli) {
$cli->send("GET / HTTP/1.1\r\n\r\n");
});
$client->on("receive", function(Client $cli, $data){
echo "Receive: $data";
});
$client->on("error", function(Client $cli){
echo "error\n";
});
$client->on("close", function(Client $cli){
echo "Connection close\n";
});
$client->connect('127.0.0.1', 9501);
$r = 0;
while (true) {
sleep(1);$r++;echo $r."\n";
}
效果:根据如上的代码来说就是服务端会检测客户端的访问,如果说服务端在10秒内没有收到信息,就断开客户端的连接