myesn

myEsn2E9

hi
github

[未完成] 4.13 拔掉网线后, 原本的 TCP 连接还存在吗?

拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?

可能有的同学会说,网线都被拔掉了,那说明物理层被断开了,那在上层的传输层理应也会断开,所以原本的 TCP 连接就不会存在的了。就好像, 我们拨打有线电话的时候,如果某一方的电话线被拔了,那么本次通话就彻底断了。

其实这样的认知是错误的,上面这个逻辑就有问题。问题在于,错误的认为拔掉网线这个动作会影响传输层,事实上并不会影响

实际上,TCP 连接在 Linux 内核中是一个名为 struct socket 的结构体,该结构体的内容包含 TCP 连接的状态等信息。当拔掉网线的时候,操作系统并不会变更该结构体的任何内容,所以 TCP 连接的状态也不会发生改变

可以通过比如 ssh 与远端服务器建立 TCP 连接,然后断开本机网络来模拟拔掉网线的场景,然后通过以下命令查看 TCP 连接的状态,可以发现还是 ESTABLISHED 状态:

# linux
# netstat: https://wangchujiang.com/linux-command/c/netstat.html
# or ss: https://wangchujiang.com/linux-command/c/ss.html
#
# grep: https://wangchujiang.com/linux-command/c/grep.html
sudo netstat -anp tcp | grep tcp | grep [dst_ip]

# windows 
# netstat: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat
# or TCPView: https://learn.microsoft.com/en-us/sysinternals/downloads/tcpview
#
# findstr: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/findstr
netstat -anp tcp | findstr [dst_ip]

现在,我们知道拔掉网线的动作并不会影响 TCP 连接的状态。主要是看拔掉网线后,双方做了什么动作,针对这个问题,要分场景来讨论,即拔掉网线后,有无数据传输

拔掉网线后,有数据传输#

客户端拔掉网线后,服务端向客户端发送的数据报文会得不到任何的响应,在等待一定时长后,服务端就会触发超时重传机制,重传未得到响应的数据报文。

如果在服务端重传报文的过程中,客户端刚好把网线插回去了,由于拔掉网线并不会改变客户端的 TCP 连接状态,并且还是处于 ESTABLISHED 状态,所以这时客户端是可以正常接收服务端发来的数据报文的,然后客户端就会回 ACK 响应报文。此时,客户端和服务端的 TCP 连接依然存在的,就感觉什么事情都没有发生。

但是,如果在服务端重传报文的过程中,客户端一直没有将网线插回去,服务端超时重传报文的次数达到一定阈值后,内核就会判定出该 TCP 有问题,然后通过 Socket 接口告诉应用程序该 TCP 连接出问题了,于是服务端的 TCP 连接就会断开。而等客户端插回网线后,如果客户端向服务端发送了数据,由于服务端已经没有与客户端相同四元组的 TCP 连接了,因此服务端内核就会回复 RST 报文,客户端收到后就会释放该 TCP 连接。此时,客户端和服务端的 TCP 连接都已经断开了。

Q:那 TCP 的数据报文具体重传几次呢?
A:在 Linux 系统中,提供了一个叫 tcp_retries2 配置项(/proc/sys/net/ipv4/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 增长关系如下表格:
image

拔掉网线后,没有数据传输#

针对拔掉网线后,没有数据传输的场景,还得看是否开启了 TCP keepalive 机制 (TCP 保活机制)。

如果没有开启 TCP keepalive 机制,在客户端拔掉网线后,并且双方都没有进行数据传输,那么客户端和服务端的 TCP 连接将会一直保持存在。

而如果开启了 TCP keepalive 机制,在客户端拔掉网线后,即使双方都没有进行数据传输,在持续一段时间后,TCP 就会发送探测报文:

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。