本文由作者小林coding分享,来自公号“小林coding”,有修订和改动。
1、引言
说到TCP协议,对于从事即时通讯/IM这方面应用的开发者们来说,再熟悉不过了。随着对TCP理解的越来越深入,很多曾今碰到过但没时间深入探究的TCP技术概念或疑问,现在是时候回头来恶补一下了。
本篇文章,我们就从系统层面深入地探讨一个有趣的TCP技术问题:拔掉网线后,再插上,原本的这条TCP连接还在吗?或者说它还“好”吗?
可能有的人会说:网线都被拔掉了,那说明物理层(也叫实体层)被断开了(关于网络协议分层模型请见《快速理解网络通信协议(上篇)》),那在物理层之上的传输层理应也会断开,所以原本的TCP连接就不会存在的了。就好像我们拨打有线电话的时候,如果某一方的电话线被拔了,那么本次通话就彻底断了。
答案真的是这样吗?可能并非你理解的这样哦,一起跟随笔者来深入探讨一下。
2、系列文章
本文是系列文章中的第14篇,本系列文章的大纲如下:
《不为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》
《不为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》
《不为人知的网络编程(三):关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT》
《不为人知的网络编程(四):深入研究分析TCP的异常关闭》
《不为人知的网络编程(五):UDP的连接性和负载均衡》
《不为人知的网络编程(六):深入地理解UDP协议并用好它》
《不为人知的网络编程(七):如何让不可靠的UDP变的可靠?》
《不为人知的网络编程(八):从数据传输层深度解密HTTP》
《不为人知的网络编程(九):理论联系实际,全方位深入理解DNS》
《不为人知的网络编程(十):深入操作系统,从内核理解网络包的接收过程(Linux篇)》
《不为人知的网络编程(十一):从底层入手,深度分析TCP连接耗时的秘密》
《不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制》
《不为人知的网络编程(十三):深入操作系统,彻底搞懂.0.0.1本机网络通信》
《不为人知的网络编程(十四):拔掉网线再插上,TCP连接还在吗?一文即懂!》(*本文)
3、比较笼统的答案
3.1答案
引言里我们说到:有人认为,网线都被拔掉了,那说明物理层被断开,那么物理层之上的传输层肯定也会断开,所以原来的TCP连接自然也就不存在了。(PS:计算机网络分层详解请见《史上最通俗计算机网络分层详解》)
上面这个逻辑是有问题的。
问题在于:错误的认为拔掉网线这个动作会影响传输层,事实上并不会影响!
实际上:TCP连接在Linux内核中是一个名为structsocket的结构体,该结构体的内容包含TCP连接的状态等信息。
所以:当拔掉网线的时候,操作系统并不会变更该结构体的任何内容,所以TCP连接的状态也不会发生改变。
3.2实验验证一下
我做了个小实验:我用ssh终端连接了我的云服务器,然后我通过断开wifi的方式来模拟拔掉网线的场景,此时查看TCP连接的状态没有发生变化,还是处于ESTABLISHED状态(如下图所示)。
通过上面实验结果可以验证我的结论:拔掉网线这个动作并不会影响TCP连接的状态。
不过,这个答案还是有点笼统。实际上,我们应该在更具体的场景中来看待这个问题,答案才更准确一些。
这个具体场景就是:
1)当拔掉网线后,有数据传输时;
2)当拔掉网线后,没有数据传输时。
针对上面这两种具体的场景,我来更具体地来分析一下。我们继续往下阅读。
4、具体场景1:拔掉网线后,有数据传输时
4.1数据传输过程中,恰好又把网线插回去了
如果是客户端被拔掉网线后,服务端向客户端发送的数据报文会得不到任何的响应,在等待一定时长后,服务端就会触发TCP协议的超时重传机制(详见:《TCP/IP详解-第21章·TCP的超时与重传》),然而此时重传并不能得到响应的数据报文。
如果在服务端重传报文的过程中,客户端恰好把网线插回去了,由于拔掉网线并不会改变客户端的TCP连接状态,并且还是处于ESTABLISHED状态,所以这时客户端是可以正常接收服务端发来的数据报文的,然后客户端就会回ACK响应报文。
此时:客户端和服务端的TCP连接将依然存在且工作状态不会受到影响,给应用层的感觉就像什么事情都没有发生。。。
4.2数据传输过程中,网线一直没有插回去
上面这种情况下,如果在服务端TCP协议重传报文的过程中,客户端一直没有将网线插回去,那么服务端超时重传报文的次数达到一定阈值后,内核就会判定出该TCP有问题。然后就会通过Socket接口告诉应用程序该TCP连接出问题了,于是服务端的TCP连接就会断开。
接下来,如果客户端再插回网线,如果客户端向服务端发送了数据,由于服务端已经没有与客户端匹配的TCP连接信息了,因此服务端内核就会回复RST报文,客户端收到后就会释放该TCP连接。
此时:客户端和服务端的TCP连接已经明确被断开,原本的这个连接也就不存在了。
4.3刨根问底:TCP数据报文到底重传几次?
本着知其然更应知其所以然的精神,我们来刨根问底一下:TCP的数据报文到底有重传几次呢?
在Linux系统中,提供了一个叫tcp_retries2配置项,默认值是15(如下图所示)。
如上图所示:这个内核参数是控制TCP连接建立的情况下,超时重传的最大次数。
不过tcp_retries2设置了15次,并不代表TCP超时重传了15次才会通知应用程序终止该TCP连接,内核还会基于“最大超时时间”来判定。
每一轮的超时时间都是倍数增长的,比如第一次触发超时重传是在2s后,第二次则是在4s后,第三次则是8s后,以此类推。
内核会根据tcp_retries2设置的值,计算出一个最大超时时间。
在重传报文且一直没有收到对方响应的情况时,先达到“最大重传次数”或者“最大超时时间”这两个的其中一个条件后,就会停止重传,然后就会断开TCP连接。
PS:有关TCP超时重传机制的详细情况,可以阅读《浅析TCP协议中的疑难杂症(下篇)》。
5、具体场景2:拔掉网线后,有数据传输时
5.1场景分析
针对拔掉网线后,没有数据传输的场景,还得具体看看是否开启了TCPKeepAlive机制(详见《彻底搞懂TCP协议层的KeepAlive保活机制》)。
1)如果没有开启TCPKeepAlive机制:
在客户端拔掉网线后,并且双方都没有进行数据传输,那么客户端和服务端的TCP连接将会一直保持存在。
2)如果开启了TCPKeepAlive机制:
在客户端拔掉网线后,即使双方都没有进行数据传输,在持续一段时间后,TCP就会发送KeepAlive探测报文。
根据KeepAlive探测报文响应情况,会有以下两种可能:
1)如果对端正常工作:当探测报文被对端收到并正常响应,TCP保活时间将被重置,等待下一个TCP保活时间的到来;
2)如果对端主机崩溃或对端由于其他原因导致报文不可达:当探测报文发送给对端后,石沉大海、没有响应,连续几次,达到保活探测次数后,TCP会报告该连接已经死亡。
所以:TCP保活机制可以在双方没有数据交互的情况,通过TCPKeepAlive机制的探测报文,来确定对方的TCP连接是否存活。
5.2刨根问底:TCPKeepAlive机制具体是什么样的?
TCPKeepAlive机制的原理是这样的:
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP保活机制会开始作用,每隔一个时间间隔,发送一个探测报文。该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的TCP连接已经死亡,系统内核将错误信息通知给上层应用程序。
在Linux内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔。
以下是Linux中的默认值:
net.ipv4.tcp_keepalive_time=
net.ipv4.tcp_keepalive_intvl=75net.ipv4.tcp_keepalive_probes=9解释一下:
1)tcp_keepalive_time=:表示保活时间是秒(2小时),也就2小时内如果没有任何连接相关的活动,则会启动保活机制;
2)tcp_keepalive_intvl=75:表示每次检测间隔75秒;
3)tcp_keepalive_probes=9:表示检测9次无响应,认为对方是不可达的,从而中断本次的连接。
也就是说在Linux系统中,最少需要经过2小时11分15秒才可以发现一个“死亡”连接。
计算公式是:
注意:应用程序若想使用TCP保活机制需要通过socket接口设置SO_KEEPALIVE选项才能够生效,如果没有设置,那么就无法使用TCP保活机制。
PS:关于TCP协议的KeepAlive机制详见《彻底搞懂TCP协议层的KeepAlive保活机制》、《一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等》。
5.3刨根问底:TCPKeepAlive机制的探测时间也太长了吧?
没错,确实有点长。
TCPKeepAlive机制是TCP层(内核态)实现的,它是给所有基于TCP传输协议的程序一个兜底的方案。
实际上:我们通常在应用层自己实现一套探测机制,可以在较短的时间内,探测到对方是否存活。
比如:一般Web服务器都会提供keepalive_timeout参数,用来指定HTTP长连接的超时时间。如果设置了HTTP长连接的超时时间是60秒,Web服务软件就会启动一个定时器,如果客户端在完后一个HTTP请求后,在60秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接。
再比如:IM、消息推送系统里的心跳机制,通过应用层的心跳机制(由客户端发出,服务端回复响应包),来灵活控制和探测长连接的健康度。
《为何基于TCP协议的移动端IM仍然需要心跳保活机制?》这篇文章解释了IM这类应用中应用层心跳保活的必要性,有兴趣可以读一读。
如果对应用层心跳的具体应用没什么概念,可以看看