你们好,我是小林。服务器
以前收到个读者的问题,对于 TCP 三次握手和四次挥手的一些疑问:app
第一次握手,若是客户端发送的SYN一直都传不到被服务器,那么客户端是一直重发SYN到永久吗?客户端中止重发SYN的时机是什么?tcp
第三次握手,若是服务器永远不会收到ACK,服务器就永远都留在 Syn-Recv 状态了吗?退出此状态的时机是什么?函数
第三次挥手,若是客户端永远收不到 FIN,ACK,客户端永远停留在 Fin-Wait-2状态了吗?退出此状态时机是何时呢?spa
第四次挥手,若是服务器永远收不到 ACK,服务器永远停留在 Last-Ack 状态了吗?退出此状态的时机是什么呢?操作系统
若是客户端 在 2SML内依旧没收到 FIN,ACK,会关闭连接吗?服务器那边怎么办呢,是怎么关闭连接的呢?code
能够看到,这些问题都是关于 TCP 是如何处理这些异常场景的,咱们在学 TCP 链接创建和断开的时候,老是觉得这些过程能如期完成。orm
惋惜理想很丰满,现实很骨感,事实预料呀。blog
TCP 固然不傻,对以上这些异常场景都是有作处理的。进程
此次就针对读者问的这一系列问题,来详细说说 TCP 是怎么处理这些异常的?
这些异常场景共分为两大类,第一类是 TCP 三次握手期间的异常,第二类是 TCP 四次挥手期间的异常。
咱们先来看看 TCP 三次握手的过程。
当客户端想和服务端创建 TCP 链接的时候,首先第一个发的就是 SYN 报文,而后进入到 SYN_SENT
状态。
在这以后,若是客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发超时重传机制。
不一样版本的操做系统可能超时时间不一样,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,若是想要更改则须要从新编译内核,比较麻烦。
当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几回呢?
在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries
内核参数控制,这个参数是能够自定义的,默认值通常是 5。
一般,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
当第五次超时重传后,会继续等待 32 秒,若是服务端仍然没有回应 ACK,客户端就再也不发送 SYN 包,而后断开 TCP 链接。
因此,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。
当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD
状态。
第二次握手的 SYN-ACK
报文其实有两个目的 :
因此,若是第二次握手丢了,就会发送比较有意思的事情,具体会怎么样呢?
由于第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,因此,若是客户端迟迟没有收到第二次握手,那么客户端就以为可能本身的 SYN 报文(第一次握手)丢失了,因而客户端就会触发超时重传机制,重传 SYN 报文。
而后,由于第二次握手中包含服务端的 SYN 报文,因此当客户端收到后,须要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。
那么,若是第二次握手丢失了,服务端就收不到第三次握手,因而服务端这边会触发超时重传机制,重传 SYN-ACK 报文。
在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries
内核参数决定,默认值是 5。
所以,当第二次握手丢失了,客户端和服务端都会重传:
tcp_syn_retries
内核参数决定。;tcp_synack_retries
内核参数决定。客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH
状态。
由于这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,因此当第三次握手丢失了,若是服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
咱们再来看看 TCP 四次挥手的过程。
当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开链接,此时客户端的链接进入到 FIN_WAIT_1
状态。
正常状况下,若是能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2
状态。
若是第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries
参数控制。
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries
后,就再也不发送 FIN 报文,直接进入到 close
状态。
当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的链接进入到 CLOSE_WAIT
状态。
在前面咱们也提了,ACK 报文是不会重传的,因此若是服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2
状态,在这个状态须要等服务端发送第三次挥手,也就是服务端的 FIN 报文。
对于 close 函数关闭的链接,因为没法再发送和接收数据,因此FIN_WAIT2
状态不能够持续过久,而 tcp_fin_timeout
控制了这个状态下链接的持续时长,默认值是 60 秒。
这意味着对于调用 close 关闭的链接,若是在 60 秒后尚未收到 FIN 报文,客户端(主动关闭方)的链接就会直接关闭。
当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时链接处于 CLOSE_WAIT
状态,顾名思义,它表示等待应用进程调用 close 函数关闭链接。
此时,内核是没有权利替代进程关闭链接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。
服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时链接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认链接关闭。
若是迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retrie
s 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是同样的。
当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端链接进入 TIME_WAIT
状态。
在 Linux 系统,TIME_WAIT 状态会持续 60 秒后才会进入关闭状态。
而后,服务端(被动关闭方)没有收到 ACK 报文前,仍是处于 LAST_ACK 状态。
若是第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries
参数控制。
是吧,TCP 聪明着很!