当客户端想和服务端建立 tcp 连接的时候,首先第一个发的就是 syn 报文,然后进入到 syn_sent 状态。在这之后,如果客户端迟迟收不到服务端的 syn-ack 报文(第二次握手),就会触发「超时重传」机制,重传 syn 报文,而且重传的 syn 报文的序列号都是一样的。
当客户端想和服务端建立 tcp 连接的时候,首先第一个发的就是 syn 报文,然后进入到 syn_sent 状态。
在这之后,如果客户端迟迟收不到服务端的 syn-ack 报文(第二次握手),就会触发「超时重传」机制,重传 syn 报文,而且重传的 syn 报文的序列号都是一样的。
不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。
当客户端在 1 秒后没收到服务端的 syn-ack 报文后,客户端就会重发 syn 报文,那到底重发几次呢?
在 linux 里,客户端的 syn 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。
# cat /proc/sys/net/ipv4/tcp_syn_retries5
通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ack,客户端就不再发送 syn 包,然后断开 tcp 连接。
所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。
举个例子,假设 tcp_syn_retries 参数值为 3,那么当客户端的 syn 报文一直在网络中丢失时,会发生下图的过程:
具体过程:
第二次握手丢失了,会发生什么?
当服务端收到客户端的第一次握手后,就会回 syn-ack 报文给客户端,这个就是第二次握手,此时服务端会进入 syn_rcvd 状态。
第二次握手的 syn-ack 报文其实有两个目的 :
所以,如果第二次握手丢了,就会发生比较有意思的事情,具体会怎么样呢?
因为第二次握手报文里是包含对客户端的第一次握手的 ack 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 syn 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 syn 报文。
然后,因为第二次握手中包含服务端的 syn 报文,所以当客户端收到后,需要给服务端发送 ack 确认报文(第三次握手),服务端才会认为该 syn 报文被客户端收到了。
那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 syn-ack 报文。
在 linux 下,syn-ack 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。
# cat /proc/sys/net/ipv4/tcp_synack_retries5
因此,当第二次握手丢失了,客户端和服务端都会重传:
举个例子,假设 tcp_syn_retries 参数值为 1,tcp_synack_retries 参数值为 2,那么当第二次握手一直丢失时,发生的过程如下图:
具体过程:
第三次握手丢失了,会发生什么?
客户端收到服务端的 syn-ack 报文后,就会给服务端回一个 ack 报文,也就是第三次握手,此时客户端状态进入到 establish 状态。
因为这个第三次握手的 ack 是对第二次握手的 syn 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 syn-ack 报文,直到收到第三次握手,或者达到最大重传次数。
注意,ack 报文是不会有重传的,当 ack 丢失了,就由对方重传对应的报文。
举个例子,假设 tcp_synack_retries 参数值为 2,那么当第三次握手一直丢失时,发生的过程如下图:
具体过程:
当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 fin 报文,试图与服务端断开连接,此时客户端的连接进入到 fin_wait_1 状态。
正常情况下,如果能及时收到服务端(被动关闭方)的 ack,则会很快变为 fin_wait2状态。
如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ack 的话,也就会触发超时重传机制,重传 fin 报文,重发次数由 tcp_orphan_retries 参数控制。
当客户端重传 fin 报文的次数超过 tcp_orphan_retries 后,就不再发送 fin 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。
举个例子,假设 tcp_orphan_retries 参数值为 3,当第一次挥手一直丢失时,发生的过程如下图:
具体过程:
当客户端超时重传 3 次 fin 报文后,由于 tcp_orphan_retries 为 3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ack报文),那么客户端就会断开连接。
第二次挥手丢失了,会发生什么?
当服务端收到客户端的第一次挥手后,就会先回一个 ack 确认报文,此时服务端的连接进入到 close_wait 状态。
在前面我们也提了,ack 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 fin 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
举个例子,假设 tcp_orphan_retries 参数值为 2,当第二次挥手一直丢失时,发生的过程如下图:
具体过程:
这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ack 报文后,客户端就会处于 fin_wait2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 fin 报文。
对于 close 函数关闭的连接,由于无法再发送和接收数据,所以fin_wait2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 fin 报文,客户端(主动关闭方)的连接就会直接关闭,如下图:
但是注意,如果主动关闭方使用 shutdown 函数关闭连接,指定了只关闭发送方向,而接收方向并没有关闭,那么意味着主动关闭方还是可以接收数据的。
此时,如果主动关闭方一直没收到第三次挥手,那么主动关闭方的连接将会一直处于 fin_wait2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)。如下图:
第三次挥手丢失了,会发生什么?
当服务端(被动关闭方)收到客户端(主动关闭方)的 fin 报文后,内核会自动回复 ack,同时连接处于 close_wait 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。
此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 fin 报文。
服务端处于 close_wait 状态时,调用了 close 函数,内核就会发出 fin 报文,同时连接进入 last_ack 状态,等待客户端返回 ack 来确认连接关闭。
如果迟迟收不到这个 ack,服务端就会重发 fin 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 fin 报文的重传次数控制方式是一样的。
举个例子,假设 tcp_orphan_retries = 3,当第三次挥手一直丢失时,发生的过程如下图:
具体过程:
第四次挥手丢失了,会发生什么?
当客户端收到服务端的第三次挥手的 fin 报文后,就会回 ack 报文,也就是第四次挥手,此时客户端连接进入 time_wait 状态。
在 linux 系统,time_wait 状态会持续 2msl 后才会进入关闭状态。
然后,服务端(被动关闭方)没有收到 ack 报文前,还是处于 last_ack 状态。
如果第四次挥手的 ack 报文没有到达服务端,服务端就会重发 fin 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
举个例子,假设 tcp_orphan_retries 为 2,当第四次挥手一直丢失时,发生的过程如下:
具体过程:
友情链接