第一次握手丢失
当客户端想和服务端建立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_retries
5
通常,第一次超时重传是在1秒后,第二次超时重传是在2秒后,第三次超时重传是在4秒后...每一次超时重传的时间都是上一次的2倍
总耗时 = 1+2+4+8+16+32 = 63秒
具体过程:
- 当客户端超时重传3次SYN报文后,由于tcp_syn_retries为3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的2倍),如果还是没能收到服务器的第二次握手,那么客户端就会断开连接。
第二次握手丢失
当服务端收到客户端的第一次握手后,就会回SYN-ACK报文给客户端,这个就是第二次握手,此时服务端会进入SYN_RCVD状态。
第二次握手的SYN-ACK报文有两个目的:
- 第二次握手的ACK,是对第一次握手的确认报文
- 第二次握手的SYN,是服务器端发起建立TCP连接的报文
如果第二次握手丢失了:
因为第二次握手报文里包含对客户端的第一次握手的ACK确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的SYN报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传SYN报文。
然后,因为第二次握手中包含服务端的SYN报文,所以当客户端收到后,需要给服务端发送ACK确认报文(第三次握手),服务端才会认为该SYN报文被客户端收到了。
那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传SYN-ACK报文
在Linux下,SYN-ACK报文的最大重传次数由tcp_synack_retries内核参数决定,默认值是5
# cat /proc/sys/net/ipv4/tcp_synack_retries
5
因此,当第二次握手丢失了,客户端和服务端都会重传:
- 客户端会重传SYN报文,也就是第一次握手,最大重传次数由tcp_syn_retries内核参数决定;
- 服务端会重传SYN-ACK报文,也就是第二次握手,最大重传次数由tcp_syn_retries内核参数决定;
举个例子,假设tcp_syn_retries参数值为1,tcp_synack_retries参数值为2,那么当第二次握手一直丢失时,发生的过程如下图:
具体过程:
- 当客户端超时重传1次SYN报文后,由于tcp_syn_retries为1,已达到最大重传次数,于是再等待一段时间(时间为上次超时时间的2倍),如果还是没能收到服务端的第二次握手(SYN-ACK报文),那么客户端就会断开连接。
- 当服务端超时重传2次SYN-ACK报文后,由于tcp_synack_retries为2,已达到最大重传次数,于是再等待一段时间(时间为上次超时时间的2倍),如果还是没能收到客户端的第三次握手(ACK报文),那么服务器就会断开连接。
第三次握手丢失
客户端收到服务端的SYN-ACK报文后,就会给服务端回一个ACK报文,也就是第三次握手,此时客户端状态进入到ESTABLISH状态。
因为这个第三次我收的ACK是对第二次握手SYN的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传SYN-ACK报文。直到收到第三次握手,或者达到最大重传次数。
注意,ACK报文是不会有重传的,当ACK丢失了,就由对方重传对应的报文
具体过程:
- 当服务端超时重传2次SYN-ACK报文后,由于tcp_synack_retries为2,由于已经达到了最大重传次数,于是再等待一段时间(时间为上一次超时时间的2倍),如果还是没能收到客户端的第三次握手(ACK报文),那么服务端就会断开连接。