拔掉网线后,有数据传输
4.13 拔掉网线后, 原本的 TCP 连接还存在吗?
实际上,TCP 连接在 Linux 内核中是一个名为 struct socket 的结构体,该结构体的内容包含
TCP 连接的状态等信息。当拔掉网线的时候,操作系统并不会变更该结构体的任何 内容,所以 TCP
连接的状态也不 会发生改变。
我在我的电脑上做了个 小实验,我用 ssh 终端连接了我的云服务器, 然后我通过断开 wifi 的方式
来模拟拔掉网线的场景,此时查看 TCP 连接的状态没有发生变化 ,还是处于 ESTABLISHED 状态。

通过上面这个实验结果,我们知道了,拔掉网线这个动作并不会影响 TCP 连接的状态。
接下来,要看拔掉网线后,双方做了什么 动作。
所以, 针对这个问题,要分场景来讨论 :
拔掉网线后,有数据传输;
拔掉网线后,没有数据传输;
拔掉网线后,有数据传输
在客⼾端拔掉网线后,服务端向客⼾端发送的数据报文会得不到任何 的响应,在等待一定时⻓
后,服务端就会触发超时重传机制,重传未得到响应的数据报文。
如果在服务端重传报文的过程中,客⼾端刚好把网线插回去了,由于拔掉网线并不会改变客⼾端
的 TCP 连接状态,并且还是处于 ESTABLISHED 状态,所以这时客⼾端是可以正常接收服务端发来
的数据报文的,然后客⼾端就会回 ACK 响应报文。
此时,客⼾端和服务端的 TCP 连接依然存在的,就感觉什么事 情都没有发生。
但是,如果如果在服务端重传报文的过程中,客⼾端一直没有将网线插回去,服务端超时重传报
文的次数达到一定阈值后,内核就会判定出该 TCP 有问题,然后通过 Socket 接口告诉应用程序该
TCP 连接出问题了,于是服务端的 TCP 连接就会断开。
而等客⼾端插回网线后,如果客⼾端向服务端发送了数据,由于服务端已经没有与客⼾端相同四
元祖的 TCP 连接了,因此服务端内核就会回复 RST 报文,客⼾端收到后就会释放该 TCP 连接。
此时,客⼾端和服务端的 TCP 连接都已经断开了。
那 TCP 的数据报文具体重传几次呢?
在 Linux 系统中,提供了一个叫 tcp_retries2 配置项,默认值是 15 ,如下图:

这个内核参数是控制,在 TCP 连接建立的情况下,超时重传的最大次数。
不过 tcp_retries2 设置了 15 次,并不代表 TCP 超时重传了 15 次才会通知应用程序终止该 TCP 连
接,内核会根据 tcp_retries2 设置的值,计算出一个 timeout (
如果 tcp_retries2 =15 ,那么计算得到的 timeout = 924600 ms ),如果重传间隔 超过这 个 timeout ,则认为超过了阈值,就会停止重传,然后就会断开 TCP 连接。在发生超时重传的过程中,每一轮的超时时 间(RTO )都是倍数增⻓的,比如如 果第一轮 RTO 是
200 毫秒,那么第二轮 RTO 是 400 毫秒,第三轮 RTO 是 800 毫秒,以此类推。
而 RTO 是基于 RTT (一个包的往返时间) 来计算的,如果 RTT 较大,那么计算出来的 RTO 就越
大,那么经过几轮重传后,很快 就达到了上 面的 timeout 值了。
举个 例子,如果 tcp_retries2 =15 ,那么计算得到的 timeout = 924600 ms ,如果重传总间隔 时⻓达到了 timeout 就会停止重传,然后就会断开 TCP 连接:
如果 RTT 比较小,那么 RTO 初始值就约等于下 限 200ms ,也就是第一轮的超时时 间是 200 毫
秒,由于 timeout 总时⻓是 924600 ms ,表现出来的现象刚好就是重传了 15 次,超过了
timeout 值,从而断开 TCP 连接
如果 RTT 比较大,假设 RTO 初始值计算得到的是 1000 ms ,也就是第一轮的超时时 间是 1 秒,
那么根本不需要重传 15 次,重传总间隔 就会超过 924600 ms 。
最小 RTO 和最大 RTO 是在 Linux 内核中定义好了:
#define TCP_RTO_MAX ((unsigned )(120 *HZ ))
#define TCP_RTO_MIN ((unsigned )(HZ /5))Linux 2.6+ 使用 1000 毫秒的 HZ ,因此 TCP_RTO_MIN 约为 200 毫秒, TCP_RTO_MAX 约为 120 秒。
如果 tcp_retries 设置为 15 ,且 RTT 比较小,那么 RTO 初始值就约等于下 限 200ms ,这意味着
它需要 924.6 秒才能将断开的 TCP 连接通知给上层(即应用程序),每一轮的 RTO 增⻓关系如下
表格:

拔掉网线后,没有数据传输
针对拔掉网线后,没有数据传输的场景,还得看是否开启了 TCP keepalive 机制 (TCP 保活机
制)。
如果没有开启 TCP keepalive 机制,在客⼾端拔掉网线后,并且双方都没有进行数据传输,那么客
⼾端和服务端的 TCP 连接将会一直保持存在。
而如果开启了 TCP keepalive 机制,在客⼾端拔掉网线后,即使双方都没有进行数据传输,在持续
一段时间后,TCP 就会发送探测报文:
如果对端是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活
时间会被重置,等待下一个 TCP 保活时间的到来。
如果对端主机宕机(
注意不是进程崩溃,进程崩溃后操作系统在回收进程资源的时候,会发送
FIN
报文,而主机宕机则是无法感知的,所以需要 TCP
保活机制来探测对方是不是发生了主 机
宕机),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
所以,TCP 保活机制可以在双方没有数据交互 的情况,通过探测报文,来确定对方的 TCP 连接是
否存活。
TCP keepalive 机制具体是怎么样的?
这个机制的原理是这样的:
定义一个时间段,在这个时间段内,如果没有任何 连接相关的活动,TCP 保活机制会开始作用,每
隔一个时间间隔 ,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都
没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔 ,以下
都为默认值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
tcp_keepalive_time=7200 :表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何 连接相关的活动,则会启动保活机制
tcp_keepalive_intvl=75 :表示每次 检测间隔 75 秒;
tcp_keepalive_probes=9 :表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

注意,应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生
效,如果没有设置,那么就无法使用 TCP 保活机制。
TCP keepalive 机制探测的时间也太⻓了吧?
对的,是有点⻓。
TCP keepalive 是 TCP 层(内核态) 实现的,它是给所有基于 TCP 传输协议的程序一个兜底的方
案。
实际上,我们应用层可以自己实现一套探测机制,可以在较短的时间内,探测到对方是否存活。
比如,web 服务软件一般都会提供 keepalive_timeout 参数,用来指定 HTTP ⻓连接的超时时
间。如果设置了 HTTP ⻓连接的超时时 间是 60 秒,web 服务软件就会启动一个定时器,如果客⼾
端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回
调函数来释放该连接。

总结
客⼾端拔掉网线后,并不会直接影响 TCP 连接状态。所以,拔掉网线后,TCP 连接是否还会存
在,关键要看拔掉网线之后,有没有进行数据传输。
有数据传输的情况:
在客⼾端拔掉网线后,如果服 务端发送了数据报文,那么在服务端重传次数没有达到最大值之
前,客⼾端就插回了网线,那么双方原本的 TCP 连接还是能正常存在,就好像什么事 情都没有
发生。
在客⼾端拔掉网线后,如果服 务端发送了数据报文,在客⼾端插回网线之前,服务端重传次数
达到了最大值时,服务端就会断开 TCP 连接。等到客⼾端插回网线后,向服务端发送了数据,
因为服务端已经断开了与 客⼾端相同四元组的 TCP 连接,所以就会回 RST 报文,客⼾端收到后
就会断开 TCP 连接。至此, 双方的 TCP 连接都断开了。
没有数据传输的情况:
如果双方都没有开启 TCP keepalive 机制,那么在客⼾端拔掉网线后,如果客⼾端一直不插回网
线,那么客⼾端和服务端的 TCP 连接状态将会一直保持存在。
如果双方都开启了 TCP keepalive 机制,那么在客⼾端拔掉网线后,如果客⼾端一直不插回网
线,TCP keepalive 机制会探测到对方的 TCP 连接没有存活,于是就会断开 TCP 连接。而如果
在 TCP 探测期间,客⼾端插回了网线,那么双方原本的 TCP 连接还是能正常存在。
除了客⼾端拔掉网线的场景,还有客⼾端「主机宕机和进程崩溃 」的两种场景。
第一个场景,客⼾端宕机这件事跟拔掉网线是一样无法被服务端的感知的,所以如果在没有数据
传输,并且没有开启 TCP keepalive 机制时,,服务端的 TCP 连接将会一直处于 ESTABLISHED 连
接状态,直到服务端重启进程。
所以,我们可以得知一个点。在没有使用 TCP 保活机制,且双方不传输数据的情况下,一方的
TCP 连接处在 ESTABLISHED 状态时,并不代表另一方的 TCP 连接还一定是正常的。
第二个场景,客⼾端的进程崩溃后,客⼾端的内核就会向服务端发送 FIN 报文,与服务端进行四
次挥手。
所以,即使没有开启 TCP keepalive ,且双方也没有数据交互 的情况下,如果其中一方的进程发生
了崩溃,这个过程操作系统是可以感知的到的,于是就会发送 FIN 报文给对方,然后与对方进行
TCP 四次挥手。
完!
