灵魂拷问 TCP ,你要投降了吗?

[[tcp|Transmission Control Protocol]] 三次握手和四次挥手过程中,途中某一步的报文丢失了,会发生什么?

TCP 三次握手丢包

第一次握手丢失了,会发生什么

当客户端想和服务端建立 TCP 链接时,首先会发出 SYN 报文,然后进入 SYN_SENT 状态。

如果客户端迟迟收不到服务端的 SYN-ACK 报文,就会触发「超时重传」机制,重传 SYN 报文,且重传的 SYN 报文的序列号保持不变

不同版本的操作系统的超时时长可能不同,有的是 1 秒,有的是 3 秒;这个超时配置写死在内核,如果想要修改需要重新编译内核,比较麻烦。

在 [[linux]] 中,客户端的 SYN 报文的最大重传次数由 tcp_syn_retries 内核参数控制,这个参数是支持自定义的,默认值为 5.

1
2
$ cat /proc/sys/net/ipv4/tcp_syn_retries
5

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒后,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次超时重传是在  16 秒后。每次超时的时间是上一次的 2 倍

当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。

所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。

.

第二次握手丢失了,会发生什么

当服务端收到客户端的 SYN 报文后,就会回 SYN-ACK 报文给客户端,这就是第二次握手,此时服务端会进入 SYN-RCVD 状态。

第二次握手的 SYN-ACK 报文的主要目的:

  • ACK 是对第一次握手的确定报文
  • SYN 是服务端发起建立 TCP 连接的报文

如果客户端迟迟没有收到第二次握手,那么客户端就会认为自己的 SYN 报文丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文

如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端也会出发超时重传机制,重传 SYN-ACK 报文

在 [[linux]] 中,SYN-ACK 报文的最大重传次数由 tcp_synack_retries 参数决定,默认值是 5.

1
2
$ cat /proc/sys/net/ipv4/tcp_synack_retries
5

.

第三次握手丢失了,会发生什么

客户端收到服务端的 SYN-ACK 报文后,就会给服务端回 ACK 报文,也就是第三次握手,此时客户端进入到 ESTABLISH 状态。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

[!important] ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。

.

TCP 四次挥手丢包

第一次挥手丢失了,会发生什么

当客户端调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端 (被动关闭方) 的 ACK,就会很快变为 FIN_WAIT_2 状态。

如果第一次挥手丢失了,客户端迟迟收不到被动方的 ACK,就会触发超时重传机制,重传 FIN 报文,重传次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过  tcp_orphan_retries  后,就不再发送 FIN 报文,则会再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到  close  状态。

.

第二次挥手丢失了,会发生什么?

当服务端收到客户端的第一次挥手后,就会回一个 ACK 确认报文,此时服务端的连接进入到  CLOSE_WAIT  状态。

由于 ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

.

第三次挥手丢失了,会发生什么?

当服务端 (被动关闭方) 收到客户端 (主动关闭方) 的 FIN 报文后,内核会自动回复 ACK,同时连接处于  CLOSE_WAIT  状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。内核没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文

服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。

如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由  tcp_orphan_retries  参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

.

第四次挥手丢失了,会发生什么?

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入  TIME_WAIT  状态。

在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。

然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。

如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的  tcp_orphan_retries  参数控制。

.

References

Get Things Done
Built with Hugo
Theme Stack designed by Jimmy