TCP的KeepAlive探测详解

Posted LinuxerPub

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP的KeepAlive探测详解相关的知识,希望对你有一定的参考价值。

        在写TCP服务程序时,除了要处理SIGPIPE外,还要有客户端连接检测机制,用于及时发现崩溃的客户端连接。一般来说,有两种检测方式:1. 在应用层,由业务程序自己检测;2. 使用TCP的KeepAlive机制。

        使用第一种方式,意味着要在应用层自己实现一个ping-pong逻辑和协议,并支持设置空闲时长,重试次数,重试间隔等。这无疑会增加一定的代码量,好处则是可以自己控制逻辑,同时不用学习内核的实现:)

        但是如果没有特殊的需求,我更倾向于第二种方式。如非必要,不要引入额外的逻辑。更何况既然TCP本身已经支持KeepAlive,为什么还要自己实现一套,多此一举呢?代码写的越多,越可能引入Bug:D

        本文将对TCP的KeepAlive的使用和原理做比较详细的分析。先看如何使用TCP KeepAlive来检测“失联”的TCP连接。完整的测试代码位于:https://github.com/gfreewind/LinuxDetails/blob/master/networks/6.tcp_keepalive_reporter/tcp_keepalive_report.c。

        本文只截图几个关键的函数

其中SO_KEEPALIVE用于打开或者关闭KeepAlive功能,TCP_KEEPIDLE用于设置空闲时间——即有多久没有发送报文就进行探测,TCP_KEEPCNT用于设置KeepAlive的尝试次数,TCP_KEEPINTVL用于设置重试KeepAlive的报文间隔。这里容易搞混的是TCP_KEEPIDLE和TCP_KEEPINTVL,前者是需要进行KeepAlive探测的空闲时间,而后者是在某次KeepAlive探测失败,再次重试的间隔时间。对于上面的程序来说,当该TCP连接有5秒没有进行数据传输时,就会发送KeepAlive探测报文。当探测报文失败时,会隔2秒再次发送探测报文,3次探测失败就判断连接失败。

        通过测试程序,我们可以使用tcpdump抓包,来分析KeepAlive。

TCP的KeepAlive探测详解

前三个报文是TCP的三次握手,连接成功后,没有任何报文发送。间隔5秒后,发送KeepAlive,即第4个报文。第5个报文为KeepAlive ACK。再间隔5秒后,再次发送KeepAlive探测报文,即第6个报文。

(请忽略报文的黑颜色,因为这个测试是本机发给本机,所以TCP校验和是不正确的——没有真正通过网卡)

        为了测试KeepAlive检测报文失败的情况,在连接成功之后,我使用iptables创建一条规则,丢弃发往port 34567端口的数据报文。这时抓包结果如下:

TCP的KeepAlive探测详解

同上,前三个报文完成TCP三次握手,间隔5秒后发送KeepAlive探测报文,但由于没有收到ACK,所以每间隔2秒再次发送KeepAlive,重试3次后,判定连接失败,在11秒时(应该发送第4个KeepAlive时)发送RST给对端,中断连接。

        那么当KeepAlive机制判断连接崩溃时,应用层如何得到通知呢?当连接正常关闭时,应用层可以得到可读事件通知,并且进行read操作时,返回结果为0——这也是服务端判断客户端关闭连接的方法。就此推测,KeepAlive机制判断连接崩溃时,其行为应该与正常关闭类似。在测试代码中,分别使用了select和epoll来进行io事件测试,其输出如下:

TCP的KeepAlive探测详解

根据测试,无论是使用select还是epoll,当KeepAlive中止连接时,应用层都可以得到可读事件通知,并且read结果为0。上面的输出,还有一个sock err is 110的结果。这是另外一种判断机制:

TCP的KeepAlive探测详解

当KeepAlive中断连接时,其会设置socket错误,应用层通过getsockopt即SO_ERR可以获得该错误。

        通过测试程序,我们只是学到了KeepAlive的使用方法。接下来就要进入内核对KeepAlive一探究竟。

        tcp_keepalive_timer为KeepAlive定时器的回调函数。在这个函数中

TCP的KeepAlive探测详解

当探测超时,就会调用tcp_send_active_reset向对端发送RST报文,中止连接,然后调用tcp_write_err。

TCP的KeepAlive探测详解

设置sk->sk_err等于ETIMEOUT,其值就是测试程序打印的110。而sk_error_report指向的是sock_def_error_report。

其会唤醒等待在当前套接字的进程,且其IO事件是POLLERR。

而在使用epoll_ctl添加监听fd时,内核会自动把EPOLLERR和EPOLLHUP加到监听事件中。这就是为什么epoll可以得到通知。


        至此,我们已经从实现和使用上,完成了对TCP KeepAlive机制的探索。

以上是关于TCP的KeepAlive探测详解的主要内容,如果未能解决你的问题,请参考以下文章

傻傻分不清的TCP keepalive和HTTP keepalive

TCP连接探测中的Keepalive和心跳包

小记TCP keepalive

SQL Server的keepalive

TCP/IP KeepAlive

High Availability--keepalived详解篇之keepalive配置实例-master选举策略