服务器心跳机制

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。

应用场景:

在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活

什么是心跳机制?

就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。

发包方:可以是客户也可以是服务端,看哪边实现方便合理。 心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

心跳包的发送,通常有两种技术:

1.应用层自己实现的心跳包

由应用程序自己发送心跳包来检测连接是否正常,服务器每隔一定时间向客户端发送一个短小的数据包,然后启动一个线程,在线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。

2.使用SO_KEEPALIVE套接字选项

在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项. 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数

开启KeepAlive选项后会导致的三种情况:

1、对方接收一切正常:以期望的ACK响应,2小时后,TCP将发出另一个探测分节

2、对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。

3、对方无任何响应:套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭.

有关SO_KEEPALIVE的三个参数:

1.tcp_keepalive_intvl,保活探测消息的

发送频率

。默认值为75s。

发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测直到放弃探测确定连接断开的时间,大约为11min。

2.tcp_keepalive_probes,TCP发送保活探测消息以确定连接是否已断开的

次数

。默认值为9(次)。

3.tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测消息的时间,即允许的持续

空闲时间

。默认值为7200s(2h)。

总结:

一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂。用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。心跳包在按流量计费的环境下增加了费用.但TCP得在连接闲置2小时后才发送一个保持存活探测段,所以通常的方法是将保持存活参数改小,但这些参数按照内核去维护,而不是按照每个套接字维护,因此改动它们会影响所有开启该选项的套接字。

下面我们通过一个实例来展示心跳机制。

结构,一个客户程序,和一个服务程序。

步骤:

服务器:

1.经过socket、bind、listen、后用accept获取一个客户的连接请求,为了简单直观,这里服务器程序只接收一个connect请求,我们用clifd来获取唯一的一个连接。

2.为clifd修改KeepAlive的相关参数,并开启KeepAlive套接字选项,这里我们把间隔时间设为了5秒,闲置时间设置了5秒,探测次数设置为5次。

3.将clifd加入select监听的描述符号集

客户:很简单,只是连接上去,并停留在while死循环。

方式:

服务程序放到阿里云服务器上,我们执行服务程序并将输出结果重定向到一个日志文件,目的是为了将我们本地网络连接断开后,超过了keepalive闲置时间+重复发包探测的时间后,重新打开本地的网络连接,并登录服务器,通过该日志文件的内容来查看程序的打印结果。

  1 #include <iostream>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <stdlib.h>
  5 #include <strings.h>
  6 #include <stdio.h>
  7 #include <netinet/in.h>
  8 #include <arpa/inet.h>
  9 #include <errno.h>
 10 #include <unistd.h>
 11 using namespace std;
 12
 13 int main()
 14 {
 15     int skfd;
 16     if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 17         perror("");
 18         exit(-1);
 19     }
 20
 21     struct sockaddr_in saddr;
 22     bzero(&saddr, sizeof(saddr));
 23     saddr.sin_family = AF_INET;
 24     saddr.sin_port = htons(9999);
 25     saddr.sin_addr.s_addr = inet_addr("115.29.109.198");
 26
 27     if (connect(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
 28         perror("");
 29         exit(-1);
 30     }
 31
 32     cout << "连接成功" << endl;
 33     while(1);
 34     return 0;
 35 }
 36 服务器
 37
 38 #include <iostream>
 39 #include <sys/types.h>
 40 #include <sys/socket.h>
 41 #include <stdlib.h>
 42 #include <strings.h>
 43 #include <stdio.h>
 44 #include <netinet/in.h>
 45 #include <arpa/inet.h>
 46 #include <errno.h>
 47 #include <unistd.h>
 48 #include <sys/select.h>
 49 #include <netinet/tcp.h>
 50 using namespace std;
 51
 52 #define LISTENNUM 5
 53
 54 int main()
 55 {
 56     int skfd;
 57     if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 58         perror("");
 59         exit(-1);
 60     }
 61
 62     struct sockaddr_in saddr;
 63     bzero(&saddr, sizeof(saddr));
 64     saddr.sin_family = AF_INET;
 65     saddr.sin_port = htons(9999);
 66     saddr.sin_addr.s_addr = inet_addr("115.29.109.198");
 67
 68     if (bind(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
 69         perror("");
 70         exit(-1);
 71     }
 72
 73     if (listen(skfd, LISTENNUM) < 0) {
 74         perror("");
 75         exit(-1);
 76     }
 77
 78     int clifd;
 79     if ((clifd = accept(skfd, NULL, NULL)) < 0) {
 80         perror("");
 81         exit(-1);
 82     }
 83     cout << "有新连接" << endl;
 84
 85     //setsockopt
 86     int tcp_keepalive_intvl = 5;   //保活探测消息的发送频率。默认值为75s
 87     int tcp_keepalive_probes = 5;  //TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9次
 88     int tcp_keepalive_time = 5;    //允许的持续空闲时间。默认值为7200s(2h)
 89     int tcp_keepalive_on = 1;
 90
 91     if (setsockopt(clifd, SOL_TCP, TCP_KEEPINTVL,
 92         &tcp_keepalive_intvl, sizeof(tcp_keepalive_intvl)) < 0) {
 93         perror("");
 94         exit(-1);
 95     }
 96
 97     if (setsockopt(clifd, SOL_TCP, TCP_KEEPCNT,
 98         &tcp_keepalive_probes, sizeof(tcp_keepalive_probes)) < 0) {
 99         perror("");
100         exit(-1);
101     }
102
103     if (setsockopt(clifd, SOL_TCP, TCP_KEEPIDLE,
104         &tcp_keepalive_time, sizeof(tcp_keepalive_time)) < 0) {
105         perror("");
106         exit(-1);
107     }
108
109     if (setsockopt(clifd, SOL_SOCKET, SO_KEEPALIVE,
110         &tcp_keepalive_on, sizeof(tcp_keepalive_on))) {
111         perror("");
112         exit(-1);
113     }
114
115     char buf[1025];
116     int r;
117     int maxfd;
118     fd_set rset;
119     FD_ZERO(&rset);
120     sleep(5);
121     while (1) {
122         FD_SET(clifd, &rset);
123         maxfd = clifd + 1;
124         if (select(maxfd, &rset, NULL, NULL, NULL) < 0) {
125             perror("");
126             exit(-1);
127         }
128
129         if (FD_ISSET(clifd, &rset)) {
130             r = read(clifd, buf, sizeof(buf));
131             if (r == 0) {
132                 cout << "接收到FIN" << endl;
133                 close(clifd);
134                 break;
135             }
136             else if (r == -1) {
137                 if (errno == EINTR) {
138                     cout << "errno: EINTR" << endl;
139                     continue;
140                 }
141
142                    if (errno == ECONNRESET) {
143                     cout << "errno: ECONNRESET" << endl;
144                     cout << "对端已崩溃且已重新启动" << endl;
145                     close(clifd);
146                     break;
147                 }
148
149                 if (errno == ETIMEDOUT) {
150                     cout << "errno: ETIMEDOUT" << endl;
151                        cout << "对端主机崩溃" << endl;
152                     close(clifd);
153                     break;
154                 }
155
156                 if (errno == EHOSTUNREACH) {
157                     cout << "errno: EHOSTUNREACH" << endl;
158                     cout << "对端主机不可达" << endl;
159                        close(clifd);
160                     break;
161                 }
162             }
163         }
164     }
165
166     close(skfd);
167     return 0;
168 }

执行服务程序并重定向到日志文件server.log,执行客户程序,之后将网络连接断开

一段时间后(大于KeepAlive空闲时间+重复探测时间),重新打开网络连接,用ssh登录服务器,查看server.log文件.发现打印了ETIMEDOUT

,验证了在客户网络断开后,到达空闲时间时,服务器由于开启了KeepAlive选项,会向客户端发送探测包,几次还没收到客户端的回应,那么select将返回套接字可读的条件,并且read返回-1.设置相关错误,

而与之相反的情况是如果不开启KeelAlive选项,那么即使客户端网络断开超过了整个的空闲和探测时间,服务端的select也不会返回可读的条件,即应用程序无法得到通知。

时间: 2024-10-07 01:12:59

服务器心跳机制的相关文章

MapReduce剖析笔记之四:TaskTracker通过心跳机制获取任务的流程

上一节分析到了JobTracker把任务从队列里取出来并进行了初始化,所谓的初始化,主要是获取了Map.Reduce任务的数量,并统计了哪些DataNode所在的服务器可以处理哪些Split等等,将这些信息缓存起来,但还没有进行实质的分配.等待TaskTracker跟自己通信. TaskTracker一般运行于DataNode之上,下面是他的声明,可见,是一个线程类: /******************************************************* * TaskT

心跳机制

网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已 经断开,那发送数据和接收数据的时候就一定会有问题.可是如何判断这个套接字是否还可以使用呢?这个就需要在系统中创建心跳机制.其实TCP中已经为我们 实现了一个叫做心跳的机制.如果你设置了心跳,那TCP就会在一定的时间(比如你设置的是3秒钟)内发送你设置的次数的心跳(比如说2次),并且此信息不 会影响你自己定义的协议.所谓“心跳”就是定时发送一个自定义的结构体(心跳包或心跳帧),让对方知道自己“在线”. 以确保链接的

互联网推送服务原理:长连接+心跳机制(MQTT协议)

互联网推送消息的方式很常见,特别是移动互联网上,手机每天都能收到好多推送消息,经过研究发现,这些推送服务的原理都是维护一个长连接(要不不可能达到实时效果),但普通的socket连接对服务器的消耗太大了,所以才会出现像MQTT这种轻量级低消耗的协议来维护长连接,那么要如何维护长连接呢: 在写之前,我们首先了解一下为什么Android维护长连接需要心跳机制,首先我们知道,维护任何一个长连接都需要心跳机制,客户端发送一个心跳给 服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手

移动互联网消息推送原理:长连接+心跳机制(MQTT协议)

互联网推送消息的方式很常见,特别是移动互联网上,手机每天都能收到好多推送消息,经过研究发现,这些推送服务的原理都是维护一个长连接(要不不可能达到实时效果),但普通的socket连接对服务器的消耗太大了,所以才会出现像MQTT这种轻量级低消耗的协议来维护长连接,那么要如何维护长连接呢: 在写之前,我们首先了解一下为什么Android维护长连接需要心跳机制,首先我们知道,维护任何一个长连接都需要心跳机制,客户端发送一个心跳给 服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手

如何优化网络心跳机制

什么是心跳机制 最早的心跳机制用于服务器的安全备份机制,是为了防止服务器死机,而在服务器之间采用专用的端口和线路,周期性传送简短的信息,心跳就是形象的比喻.一旦 收不到对方的心跳信息,服务器可以接管对方的业务,避免业务的停滞.为了业务的顺畅进行,服务器发送的心跳信息可以非常频密. 微信的信令风暴将人们的目光导向心跳机制,那么心跳机制是怎么回事呢? 最早的心跳机制用于服务器的安全备份机制,是为了防止服务器死机,而在服务器之间采用专用的端口和线路,周期性传送简短的信息,心跳就是形象的比 喻.一旦收不

Twisted 库 TCP 服务器 心跳包demo

最近刚刚接触 twisted 库,感觉twisted 库的设计模式和平时接触的socket 通信很大不同, 感觉有点不大适应,为了增加自己对twisted 的适应度, 同时也熟悉一下心跳包的机制. 特地写了一个  基于twisted 库的 TCP  服务器 心跳包 demo. 以供练习之用. 同时也和大家分享 python 编程心得  demo 特性描述: 1   TCP服务器支持多客户端连接, 每次客户端连接之后,直接将客户端信息(IP 地址, 端口号)存储到字典当中. 并且启动Svr_uni

rabbitmq 的心跳机制&amp;应用

官方文档说: If a consumer dies (its channel is closed, connection is closed, or TCP connection is lost) without sending an ack, RabbitMQ will understand that a message wasn't processed fully and will re-queue it 即: 如果消费者进程挂掉了(channel关闭, connection关闭,或者tcp

ESFramework 开发手册(07) -- 掉线与心跳机制(转)

虽然我们前面已经介绍完了ESFramework开发所需掌握的各种基础设施,但是还不够.想要更好地利用ESFramework这一利器,有些背景知识是我们必须要理解的.就像本文介绍的心跳机制,在严峻的Internet条件下,是通信系统中不可或缺的机制之一. 在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是“掉线”.而“TCP掉线”这个问题远比我们通常所能想象的要复杂的多 —— 网络拓扑纷繁复杂.而从始节点A到终节点B之间可能要经过N多的交换机.路由器.防火墙等等硬件设备

心跳机制详解

应用场景: 在长连接下,有可能很长一段时间都没有数据往来.理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的.更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉.在这个时候,就需要我们的心跳包了,用于维持长连接,保活 什么是心跳机制? 就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开. 发包方:可以是客户也可以是服务端,看哪边实现方便合理. 心跳包之所以叫心跳包是因