TCP连接的”三次握手“和”四次挥手“

(TCP连接的”三次握手“和”四次挥手“)
TCP连接的”三次握手“和”四次挥手“图示

TCP连接的”三次握手“

三次握手
  1. 开始时,客户端和服务器端都处于CLOSE状态,【服务器为了提供服务,会主动监听某个端口,进入LISTEN状态】??????
  2. 客户端主动发送连接的 【SYN】 包,之后进入SYN-SENT状态,服务端在收到客户端发来的 【SYN】包后,回复 【SYN , ACK】包,之后进入 SYN-RCVD 状态。
  3. 客户端收到服务端发来的【SYN ,ACK】包后,可以确认对方的的存在,回复 【ACK】包,进入ESTABLISHED状态
  4. 服务端收到最后一个[ACK]包后,也进入ESTABLISHED状态

正常的三次握手之后,双端都进入ESTABLISHED状态,然后开始传输数据。

TCP连接”三次握手“的异常情况

1、客户端第一个【SYN】包丢了

  1. 如果在TCP连接中,客户端的第一个【SYN】包丢了,而此时跟服务器端并无联系,所以处理办法在客户端。
  2. 在TCP协议中,任何一端的【请求——应答】过程中,在一定时间范围内没哟接收到对方的回应的【ACK】包,就会认为是丢包,此时触发超时重传机制。
  3. 此时会重传【SYN】包,会重传3次,时间间隔分别是: 5.8s、24s、48s,三次时间大约是 76s 左右,而大多数伯克利系统将建立一个新连接的最长时间,限制为 75s。

总结:
客户端第一个【SYN】包丢了:客户端会进行三次重传【SYN】包,总的尝试时间大约在75s左右。

2、服务器端收到【SYN】回复,但是回复的【SYN,ACK】包丢了

  1. 对于客户端来说,在规定时间内没有接收到来自服务器端的回复,会认为是自己丢包了,会进行重传【SYN】包。
  2. 对于服务器端来说,发出的【SYN,SCK】迟迟没有客户端的【ACK】回复,会触发重传,此时服务端处于 SYN_RCVD 状态,会依次等待 3s、6s、12s 后,重新发送【SYN,ACK】包;而【SYN,ACK】的重传次数,不同操作系统有不同的配置,例如在 Linux 下可以通过 tcp_synack_retries 进行配置,默认值为 5。如果这个重试次数内,仍未收到【ACK】应答包,那么服务端会自动关闭这个连接。
  3. 同时由于客户端在没有收到【SYN,ACK】时,也会进行重传,当客户端重传的【SYN】收到后,会立即重新发送【SYN,ACK】包。

总结:
服务器端收到【SYN】回复,但是回复的【SYN,ACK】包丢了:客户端会进行如上的重传;服务器端也会进行重传。

3、客户端收到【SYN,ACK】包,在回复时丢失【ACK】包

  1. 对于客户端,在发送【ACK】包后进入 ESTABLISHED 状态。多数情况下,客户端进入ESTABLISHED 状态后,认为连接已建立,会立即发送数据。
  2. 对于服务端,因为没有收到【ACK】会走重传机制,但是服务端因为没有收到最后一个【ACK】包,依然处于 SYN-RCVD 状态。
  3. 当服务端处于 SYN-RCVD 状态下时,接收到客户端真实发送来的数据包时,会认为连接已建立,并进入ESTABLISHED状态。
    原因:
    当客户端在 ESTABLISHED 状态下,开始发送数据包时,会携带上一个【ACK】的确认序号,所以哪怕客户端响应的【ACK】包丢了,服务端在收到这个数据包时,能够通过包内 ACK 的确认序号,正常进入 ESTABLISHED 状态。
    总结:
    客户端收到【SYN,ACK】包,在回复时丢失【ACK】包:客户端会进入 ESTABLISHED状态,向服务器端发送数据;服务器端虽然没有接收到客户端最后发来【ACK】包,但是因为数据包携带ACK的确认序号,所以在接收到数据包时,会认为连接已经建立,变为ESTABLISHED状态。

4、客户端故意不发最后一次【SYN】包

如果客户端是恶意的,在发送【SYN】包后,并收到【SYN,ACK】后就不回复了,那么服务端此时处于一种半连接的状态,虽然服务端会通过 tcp_synack_retries 配置重试的次数,不会无限等待下去,但是这也是有一个时间周期的。
如果短时间内存在大量的这种恶意连接,对服务端来说压力就会很大,这就是所谓的 SYN FLOOD 攻击。
常见的防御 SYN 攻击的方法有如下几种:

  1. 缩短超时(SYN Timeout)时间
  2. 增加最大半连接数
  3. 过滤网关防护
  4. SYN cookies技术

TCP连接的”四次挥手“

四次挥手
  1. 开始客户端和服务器端都处于ESTABLISHED状态,两端任意一端都可以发送【FIN】包准备断开,图中为客户端发送【FIN】包,请求与服务器断开连接,在发出【FIN】后,客户端进入 FIN-WAIT-1 状态。
  2. 服务器端收到【FIN】,回复【ACK】表示接收到了信息,并从 ESTABLISHED 状态进入 CLOSED-WAIT 状态,开始做一些断开连接前的准备工作。
  3. 客户端收到服务器端回复的【ACK】消息后,进入 FIN-WAIT-2 状态。而当服务器端做好断开前的准备工作后,也会发送一个【FIN,ACK】的消息給客户端,表示服务器端已经准备好了,请求断开连接,并在发送消息后,服务器端进入 LAST-ACK 状态。
  4. 客户端在收到【FIN,ACK】消息后,会立即回复【ACK】,表示收到了消息,并进入 TIME_WAIT 状态,为了稳定和安全考虑,客户端会在 TIME-WAIT 状态等待 2MSL 的时长,最终进入 CLOSED 状态;服务器端收到客户端回复的【ACK】消息后,直接从 LAST-ACK 状态进入 CLOSED 状态。

经过了“四次挥手”之后,双端都为CLOSED 状态,双端正式断开了连接。

TCP连接“四次挥手”的异常情况(以客户端首先发送【FIN】为例)

1、断开连接的【FIN】包丢失

  1. 对于客户端,【FIN】发送后,进入 FIN-WAIT-1 状态,长时间没有接收到服务器端发送的【ACK】回复,会触发超时重传机制,重新向服务器端发送【FIN】包,直到触发重传的次数,关闭连接。
  2. 对于服务器端,如果没有收到【FIN】,没有任何感知,会在一段时间后,也关闭连接。

总结:
断开连接的【FIN】包丢失:客户端超时重传,重新发送【FIN】包,直至触发重传次数,关闭连接。

2、服务端第一次回复的【ACK】丢包

  1. 对于客户端,没有收到服务器端返回的【ACK】包,会触发会触发超时重传机制,重新向服务器端发送【FIN】包,直到触发重传的次数,关闭连接。
  2. 对于服务器端,收到客户端的重传【FIN】包后,又会立即再重传【ACK】。发送过【ACK】之后,进入 CLOSED-WAIT 状态,开始做断开连接前的准备工作,当准备好之后,会回复【FIN,ACK】,而这个消息是携带了之前【ACK】的响应序号的。只要这个消息没丢,客户端可以凭借【FIN,ACK】包中的响应序号,直接从 FIN-WAIT-1 状态,进入 TIME-WAIT 状态,开始长达 2MSL 的等待。

总结:
服务端第一次回复的【ACK】丢包:客户端超时重传,重新发送【FIN】包。服务器端发送【ACK】后,进入CLOSED-WAIT 状态,做断开连接前的准备,准备完毕后,如果发送的【FIN,ACK】顺利传输,则正常进行。

3、服务端发送的 【FIN,ACK】 丢包

服务端在超时后会重传,此时客户端有两种情况,要么处于 FIN-WAIT-2 状态(之前的 ACK 也丢了),会一直等待;要么处于 TIME-WAIT 状态,会等待 2MSL 时间。

也就是说,在一小段时间内客户端还在,客户端在收到服务端发来的「FIN,ACK」包后,也会回复一个「ACK」应答,并做好自己的状态切换。

也就是说,在一小段时间内客户端还在,客户端在收到服务端发来的「FIN,ACK」包后,也会回复一个「ACK」应答,并做好自己的状态切换。

4、客户端最后回复的【ACK】丢包

  1. 对于客户端,在回复【ACK】后,会进入 TIME-WAIT 状态,开始长达 2MSL 的等待。
  2. 对于服务器端,因为没有收到【ACK】的回复,会重试一段时间,直到服务器端重试超时后主动断开;或者等待新的客户端接入后,收到服务器端重试的【FIN】消息后,回复【RST】消息,在收到【RST】消息后,复位服务器端的状态。

5. 客户端收到【ACK】后,服务端跑路了

客户端在收到「ACK」后,进入了 FIN-WAIT-2 状态,等待服务端发来的「FIN」包,而如果服务端跑路了,这个包永远都等不到。

在 TCP 协议中,是没有对这个状态的处理机制的。但是协议不管,系统来凑,操作系统会接管这个状态,例如在 Linux 下,就可以通过 tcp_fin_timeout 参数,来对这个状态设定一个超时时间。

需要注意的是,当超过 tcp_fin_timeout 的限制后,状态并不是切换到 TIME_WAIT,而是直接进入 CLOSED 状态。

参考:《关于FIN_WAIT2》

6. 客户端收到 ACK 后,客户端自己跑路了

客户端收到「ACK」后直接跑路,服务端后续在发送的「FIN,ACK」就没有接收端,也就不会得到回复,会不断的走 TCP 的超时重试的机制,此时服务端处于 LAST-ACK 状态。

那就要分 2 种情况分析:

在超过一定时间后,服务端主动断开。
收到「RST」后,主动断开连接。
「RST」消息是一种重置消息,表示当前错误了,应该回到初始的状态。如果客户端跑路后有新的客户端接入,会在此发送「SYN」以期望建立连接,此时这个「SYN」将被忽略,并直接回复「FIN,ACK」消息,新客户端在收到「FIN」消息后是不会认的,并且会回复一个「RST」消息。

2.2 2MSL等待状态
TIME_WAIT状态也成为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

常见面试题

  1. 【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

  1. 【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

  1. 【问题3】为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

   现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
  1. 【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。