不为人知的网络编程:彻底搞懂TCP协议层的KeepAlive保活机制

Posted 即时通讯技术圈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不为人知的网络编程:彻底搞懂TCP协议层的KeepAlive保活机制相关的知识,希望对你有一定的参考价值。

文中引用了参考资料中的部分内容,本文参考资料详见文末“参考资料”一节,感谢资料分享者。


1、引言


对于即时通讯网社区里的IM开发者而言,网络保活这件事再熟悉不过了,比如这是最近一篇有关网络保活话题文章《》,以及社区里大量的代码实战编码中也都必须要考虑这个问题的实现,比如最近的这篇《》。

对于IM这种应用而言,应用层的网络保活的最直接办法就是心跳机制,比如主流的IM里有微信、QQ、钉钉、易信等等,可能代码实现细节有所差异,但理论上无一例外都是这样实现。(PS:没错,当初
微信跟运营商间的“信令危机”就是跟这个有关

所谓的网络心跳,通常是客户端每隔一小段时间向服务器发送一个数据包(即心跳包),通知服务器自己仍然在线(心跳包中同时可能传输一些必要的数据)。发送心跳包,从通信层面来说就是为了保持长连接,至于这个包的内容,是没有什么特别规定的,但在移动端IM中为了省流量,一般都是很小的包(比如某些第3方的IM云为了说明心跳不费流量,号称1字节的心跳包)。

但经常有人会问到,既然TCP协议本身有KeepAlive保活这个东西见:《TCP/IP详解 卷1 第23章·TCP的保活定时器,为什么还要自已在应用层去实现网络保活/心跳机制呢?

没错,通常面视即时通讯/IM方面的程序员时,这几乎是必提问题!

要解答这个问题,我通常建议看看《为什么说基于TCP的移动端IM仍然需要心跳保活?》这篇。但限于篇幅,该篇并没有深入探讨TCP协议本身的KeepAlive机制,所以这次借本文想把TCP协议的KeepAlive保活机制给详细的整理出来,以便大家能深入其中一窥究竟



2、系列文章


本文是系列文章中的第12篇,本系列文章的大纲如下:

《不为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》

《不为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》

《不为人知的网络编程(三):关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT》

《不为人知的网络编程(四):深入研究分析TCP的异常关闭》

《不为人知的网络编程(五):UDP的连接性和负载均衡》

《不为人知的网络编程(六):深入地理解UDP协议并用好它》

《不为人知的网络编程(七):》

《不为人知的网络编程(八):》

《不为人知的网络编程(九):》

《不为人知的网络编程(十):》

《不为人知的网络编程(十一):》

《不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制》(* 本文


3、TCP KeepAlive的初衷


采用TCP连接的C/S模式应用中,当连接的双方在连接空闲状态时,如果任意一方意外崩溃、当机、网线断开或路由器故障,另一方无法得知TCP连接已经失效。

那么,连接的另一方并不知道对端的情况,它会一直维护这个连接。而作为“服务端”来说,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。

所以各端要做到快速感知失败,减少无效链接操作,这就有了TCP的KeepAlive保活探测机制。

PS:这样宽泛的说TCP的KeepAlive机制的必要性,貌似还不是很有说服力,下节将带着具体的例子深入分析。


4、从NAT角度更具体地理解TCP KeepAlive的必要性


讲到TCP的KeepAlive的必要性,多数文章都是像上节这样比较笼统的进行说明,但对于爱刨根问底的开发者来说,这还远远不够。

本节将以路由器的NAT机制这个角度来具体分析TCP协议的造物主们设计KeepAlive机制的必要性。

4.1从NAT原理讲起

4.2三层地址转换

4.3传输层端口转换表

4.4问题来了


在网关C上,由于端口数量有限(0~65535),端口转换表的维护占用系统资源,因此不能无休止地向端口转换表中增加记录。对于过期的记录,网关需要将其删除。

如何判断哪些是过期记录?

网关认为:一段时间内无活动的连接是过期的,应定时检测转换表中的非活动连接,并将之丢弃。而这个丢弃的过程,网关不会以任何的方式通告该连接的任何一端。

通过下图可以更直观的理解这个过程:
不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制
▲ 上图引用自《TCP保活(TCP keepalive)

那么问题就来了:如果一个客户端应用程序由于业务需要,需要与服务端维持长连接(例如基于TCP的IM聊天应用),而如果在特别长的时间内这个连接没有任何的数据交换,网关会认为这个连接过期并将这个连接从端口转换表中丢弃。该连接被丢弃时,客户端和服务端对此是完全无感知的。在连接被丢弃后,客户端将收不到服务端的数据推送,客户端发送的数据包也不能到达服务端。

一个具体的例子来感受一下这个问题的严重性:

某财务应用,在客户端需要填写大量的表单数据,在客户端与服务器端建立TCP连接后,客户端终端使用者将花费几分钟甚至几十分钟填写表单相关信息,终端使用者终于填好表单所需信息后,点击“提交”按钮。

结果,这个时候由于中间设备早已经将这个TCP连接从连接表中删除了,其将直接丢弃这个报文或者给客户端发送RST报文,应用故障产生,这将导致客户端终端使用者所有的工作将需要重新来过,给使用者带来极大的不便和损失。


4.5解决方法


针对上述问题,TCP协议这一层的解决方法就是利用KeepAlive机制维持长连接,让网关认为我们的TCP连接是活动的,从而避免网关“干掉”我们的长连接。

通过NAT这个具体的例子,相信你已经能更具体地理解TCP协议中KeepAlive保活机制的必要性了。


5、TCP Keepalive工作原理


5.1技术原理


当一个 TCP 连接建立之后,启用 TCP Keepalive 的一端便会启动一个计时器,当这个计时器数值到达 0 之后(也就是经过tcp_keep-alive_time时间后,这个参数之后会讲到),一个 TCP 探测包便会被发出。这个 TCP 探测包是一个纯 ACK 包(RFC1122#TCP Keep-Alives规范建议:不应该包含任何数据,但也可以包含1个无意义的字节,比如0x0),其 Seq号 与上一个包是重复的,所以其实探测保活报文不在窗口控制范围内。

如果一个给定的连接在两小时内(默认时长)没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于下表中的4个状态之一。


不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制

详细解释一下就是:

1)客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。

2)客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

3)客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。

4)客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探测的响应。

直观来说,TCP KeepAlive的交互过程大致如下图所示

不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制
▲ 上图引用自《TCP保活(TCP keepalive)

5.2具体使用举例


以linux内核为例,应用程序若想使用TCP Keepalive,需要设置SO_KEEPALIVE套接字选项才能生效。

对应的,有三个重要的参数:

1)tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h);

2)tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次);

3)tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。


上面谈的是linux内核参数的配置,实际上其他编程语言有相应的设置方法。

例如,Java的Netty服务器框架中也提供了相关接口:

ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioserverSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100)// 心跳监测 .childOption(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } });// Start the server. ChannelFuture f = b.bind(port).sync();// Wait until the server socket is closed. f.channel().closeFuture().sync();

PS:Java程序只能做到设置SO_KEEPALIVE选项,至于TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等参数配置,应用层面是没法设置的。


6、TCP KeepAlive可能导致的问题


Keepalive 技术只是TCP协议中的一个可选项。因为不当的配置可能会引起一些问题,所以默认是关闭的。

具体来说,可能导致下列问题:

1)在短暂的故障期间,Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接;

2)需要消耗额外的宽带和流量(对于现在这个时代来说,这貌似已经不是问题了);

3)在以流量计费的互联网环境中增加了费用开销。


7、TCP KeepAlive在移动网络时代的局限性


不可否认,TCP协议作为TCP/IP协议族中最重要部分,对互联的发展确实功不可没(见:《技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点))。

但如今移动网络时代,无线通信越来越普及,作为上个世纪中期发明的TCP协议来说,客观的讲,在某些场景下确实有先天不足(见:《
5G时代已经到来,TCP/IP老矣,尚能饭否?)。

那么,又回到了本文开头的问题——“既然TCP协议本身有KeepAlive,为什么还要自已在应用层实现网络保活/心跳机制?”。

以移动端IM应用为例:

1)一方面,运营商ISP的网络资源更为稀缺,TCP协议默认2小时的KeepAlive基本不可能实现IM长连接“保活”(为了提升无线网络资源的利用率,运营商长则几分钟,短则数十秒就有可能回收空闲的网络连接)。

2)另一面,无线网络本身存在弱网问题,即使TCP连接是“好的”,但实际上处于“假死”状态,也无法起到长连接该有的作用。


所以说,IM应用层自已做网络保活(心跳机制)是不可避免的。

有关这方面的更多资料,有兴趣,可以深入阅读下面这几篇:

《为何基于TCP协议的移动端IM仍然需要心跳保活机制?》

《移动端IM开发者必读(一):》

《移动端IM开发者必读(二):》

《IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!》

《IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!》


8、知识拓展:TCP Keepalive和HTTP Keep-Alive有什么区别?


很多人会把TCP Keepalive 和 HTTP Keep-Alive 这两个概念搞混淆。

这里简单介绍下HTTP Keep-Alive 。

在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个html或其他类型的 Web页中包含有其他的Web资源,如javascript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。

但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加上Connection、Keep-Alive字段。

如下图所示:


HTTP 1.0 和 1.1 在 TCP连接使用方面的差异如下图所示:


通俗地总结一下:

1)HTTP的Keep-Alive是为了让TCP连接活得更久一点,在发起多个http请求时能复用同一个连接,提高通信效率;

2)TCP的KeepAlive机制意图在于探测连接的对端是否存活,是一种检测TCP连接状况的保鲜机制。


9、参考资料


[1] TCP保活(TCP keepalive)
[2] TCP协议的KeepAlive机制与HeartBeat心跳包
[3] HTTP keep-alive和TCP keepalive的区别,你了解吗?
[4] TCP KeepAlive 与 HTTP Keep-Alive 区别
[5] tcp连接探测Keepalive和心跳包
[6] TCP keepalive的探究 (1) : NAT和保活机制
[7] 理解TCP长连接(Keepalive)
[8] 为何基于TCP协议的移动端IM仍然需要心跳保活机制?
[9] 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
[10] IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!


以上是关于不为人知的网络编程:彻底搞懂TCP协议层的KeepAlive保活机制的主要内容,如果未能解决你的问题,请参考以下文章

大神带你彻底搞懂前端TCP协议

015十分钟搞懂计算机网络TCP协议

万字长文,一文搞懂TCP/IP和HTTPHTTPS

一文彻底搞懂 TCP三次握手四次挥手过程及原理

一文彻底搞懂 TCP三次握手四次挥手过程及原理

让你彻底搞懂websocket