TCP保活:心跳包/乒乓包/SO_KEEPALIVE

版权声明:guojawee https://blog.csdn.net/weixin_36750623/article/details/83547108

引言:

  1. 长连接断开后一直占用系统资源,可以通过心跳包判断连接是否断开;使用心跳包检测到连接已经死了,就断开连接。
  2. 总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

TCP保活机制

1.心跳包

由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。
注意:心跳包一般都是很小的包,或者只包含包头的一个空包

2.乒乓包

举例:微信朋友圈有人评论,客户端怎么知道有人评论?服务器怎么将评论发给客户端的?

  1. 微信客户端每隔一段时间就向服务器询问,是否有人评论?
  2. 当服务器检查到有人给评论时,服务器发送一个乒乓包给客户端,该乒乓包中携带的数据是[此时有人评论的标志位]
    注:步骤1和2,服务器和客户端不需要建立连接,只是发送简单的乒乓包。
  3. 当客户端接收到服务器回复的带有评论标志位的乒乓包后,才真正的去和服务器通过三次握手建立连接;建立连接后,服务器将评论的数据发送给客户端。

注意:乒乓包是携带很简单的数据的包


3.设置TCP属性: SO_KEEPALIVE

1.因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。
2.不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。
3.因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。
4.因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。

在《UNIX网络编程第1卷》中也有详细的阐述:
SO_KEEPALIVE:保持连接,检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive
probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:

  1. 对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。
  2. 对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接口本身则被关闭。
  3. 对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为EHOSTUNREACH。

根据上面的介绍可以知道对端以一种非优雅的方式断开连接的时候,可以设置SO_KEEPALIVE属性使得在2小时以后发现对方的TCP连接是否依然存在。如果不能接受如此之长的等待时间,从TCP-Keepalive-HOWTO上可以知道一共有两种方式可以设置:

  1. 修改内核关于网络方面的配置参数
  2. SOL_TCP字段的TCP_KEEPIDLE,TCP_KEEPINTVL,TCP_KEEPCNT三个选项
int                 keepIdle = 6;     /*开始首次KeepAlive探测前的TCP空闭时间 */
int                 keepInterval = 5; /* 两次KeepAlive探测间的时间间隔  */
int                 keepCount = 3;    /* 判定断开前的KeepAlive探测次数 */
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount)); 

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/83547108