Lab | TCP建立连接后链路断开了会发生什么

  • TCP连接是虚拟的,抽象的
  • 对底层链路出现的断开是无感知的,会导致出现假连接
  • KILL命令直接杀死服务端程序后的连接状态
  • 断网断电后的连接状态

TCP的连接是一个抽象的概念,RFC793文档中将连接称之为sockets,序号,窗口大小这类信息的组合。

The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.

实际上可以这么理解:连接就是在通信双端传输状态(状态机)与状态对应信息(序号,窗口,选项等等)的维持,它是抽象的,TCP端对端通信实际上不存在一条可靠的物理连接,它总要依托下层不可靠的IP层,数据链路层。因此,出现了这个疑问:当连接建立后,物理链路断开了会发生什么?

  • 使用kill -9直接杀死服务端程序
  • 拔网线,服务器断电

KILL命令直接杀死服务端程序后的连接状态

  • Server启动服务程序
  • tcpdump在Client侧进行抓包
  • Client连接服务程序
  • 杀死服务程序

在Server上使用netcat启动一个服务程序:

1
# nc -l 9999

在Client侧使用tcpdump抓包:

1
# tcpdump -i any -n -# port 9999

再使用netcat作为Client连接Server的服务程序:

1
2
# nc -vn 192.168.0.108 9999
Connection to 192.168.0.108 9999 port [tcp/*] succeeded!

使用netstat命令查看连接信息

1
2
3
4
# netstat -antlp | grep 9999
tcp 0 0 0.0.0.0:9999 0.0.0.0:* LISTEN 12650/nc
tcp 0 0 192.168.0.108:53756 192.168.0.108:9999 ESTABLISHED 12671/nc
tcp 0 0 192.168.0.108:9999 192.168.0.108:53756 ESTABLISHED 12650/nc

可以看出Server的服务程序PID=12650,第二条记录为Client到Server的连接,第三条为Server到Client的连接。

杀死服务程序kill -9 12650后,再次查看连接信息:

1
2
3
# netstat -antlp | grep 9999
tcp 0 0 192.168.0.108:53756 192.168.0.108:9999 CLOSE_WAIT 12671/nc
tcp 0 0 192.168.0.108:9999 192.168.0.108:53756 FIN_WAIT2 -

杀死进程使得Server发送FIN请求,连接状态变为FIN_WAIT1->FIN_WAIT2。Client上的连接变为CLOSE_WAIT,说明它收到了FIN请求并回复ACK。

查看tcpdump抓包信息:

1
2
3
4
5
1  08:03:47.983746 IP 192.168.0.108.58274 > 192.168.0.108.distinct: Flags [S], seq 2732082131, win 43690
2 08:03:47.983789 IP 192.168.0.108.distinct > 192.168.0.108.58274: Flags [S.], seq 2892591615, ack 2732082132, win 43690
3 08:03:47.983824 IP 192.168.0.108.58274 > 192.168.0.108.distinct: Flags [.], ack 1, win 342
4 08:04:01.168828 IP 192.168.0.108.distinct > 192.168.0.108.58274: Flags [F.], seq 1, ack 1, win 342
5 08:04:01.169328 IP 192.168.0.108.58274 > 192.168.0.108.distinct: Flags [.], ack 2, win 342

前三行是三次握手。后两行正是Server向Client发送的FIN请求。

Server产生FIN请求的原因是,操作系统在进程退出的时候,会对其所有打开的文件描述符执行close()。对于TCP文件描述符而言,就是关闭连接,发送FIN请求,等待四次挥手的完成。

《unix 网络编程》里面写“TCP FIN sent by kernel when client is killed or crashed”。当程序被kill的时候,内核会发送fin包给对端。这样对端进入close_wait的状态,若epoll注册了HUP的事件,把连接关闭close_wait变为close;若没有处理,对端就有一个close_wait的状态,占用了fd。

在经过了一段时间之后,再次查看连接状态:

1
2
# netstat -antlp | grep 9999
tcp 0 0 192.168.0.108:53756 192.168.0.108:9999 CLOSE_WAIT 12671/nc

发现只剩下Client的连接,Server上FIN_WAIT2状态的连接已经消失。tcpdump抓包得知,Client并没有向Server发送FIN请求,那么也就说明Server的FIN_WAIT2状态有等待超时的能力,能够在超时后,自动销毁连接。

当Client在Server连接被销毁后,再次发送数据时,则会收到Server的RST请求。

1
2
6  08:06:28.504828 IP 192.168.0.108.58274 > 192.168.0.108.distinct: Flags [P.], seq 1:3, ack 2, win 342
7 08:06:28.504888 IP 192.168.0.108.distinct > 192.168.0.108.58274: Flags [R], seq 2892591617, win 0

断网断电后的连接状态

这里使用两台机器进行试验,中间通过Router进行非直连连接。

1
Client(188) ===A=== Router ===B=== Server(108)

Server启动9999断开服务

1
# nc -l 9999

Client连接Server的9999服务

1
# nc -vn 192.168.0.108 9999

拔掉Server的网线后,在Client侧查看连接状态,依旧是ESTABLISHED状态

1
2
# netstat -altn | grep 9999
tcp4 0 0 192.168.0.188.52210 192.168.0.108.9999 ESTABLISHED

如果Client此时尝试发送数据,Client侧会不断进行重传,直到最后发送一个RST请求,报超时异常后,退出Client连接程序,销毁连接。在恢复Server网络,并等待一段时候后,使用netstat查看Server的连接状态,会发现Server到Client的连接将会一直都是ESTABLISHED状态。

1
2
3
# netstat -anltp | grep 9999
tcp 0 0 0.0.0.0:9999 0.0.0.0:* LISTEN 10188/nc
tcp 0 0 192.168.0.108:9999 192.168.0.188:52210 ESTABLISHED 10188/nc

断电的操作和断网的操作是类似的。结果如下:

  • Server断网后,若Server和Client均无数据传输,则Server网络恢复后(断开时长不超过TCP连接超时时间),双方已经能在原来的连接状态(ESTABLISHED)上继续传输数据。
  • Server断网后,若Client继续发送数据将会超时导致程序退出连接断开(会最后发出一个RST报文段)。至少是2MSL后,恢复Server网络,发现Server上的连接将会一直存在(暂不考虑保活机制),这就是假连接现象,假连接会一直占用资源无法释放,为了解决这个问题,TCP引入保活机制(Keepalive),对没有进行通信的连接每隔一段时间进行检查,对失效的连接进行主动销毁。
  • Server断电后,若Client无数据传输,则Client上的连接状态(ESTABLISHED)将一直保持。但是,很明显的,因为Server侧断电后丢失了连接信息,这个连接已经无法再被重新使用。

FIN_WAIT2的超时时间

Server主动关闭连接,在进入FIN_WAIT2阶段后,需要Client主动发送FIN请求,表示Client侧的连接可以关闭了。但是,如果Client一直不发送FIN请求呢?操作系统在实现层面会给FIN_WAIT2状态设置超时时间,通过调整内核参数net.ipv4.tcp_fin_timeout进行设置。在超时后,将会直接销毁连接。

实际上,服务器会经常遇到大量FIN_WAIT2状态连接不能及时释放的情况。原因本质上都是服务器作为主动关闭方,Client没有主动发送FIN请求。

总结

试验体现了TCP连接是抽象的,它可以在物理链路断开后依旧保持状态。连接的生命周期就是双端状态机的协商与转换。底层链路的短时间断开,TCP连接是无感知的,这时的连接是假连接。但是双端只要不进行通信,不破坏断开前的连接信息,链路恢复后,假连接又会恢复为有效的连接,双端依旧可以在此连接上继续通信。

参考


Lab | TCP建立连接后链路断开了会发生什么
http://www.tung7.com/实践出真知/TCP建立连接后链路断开了会发生什么.html
Author
Tung7
Posted on
July 16, 2021
Licensed under