TCP 三次握手和四次挥手

引言

TCP 三次握手和四次挥手应该是每个涉及到网络的 IT 人员应该要理解的。
如果想在技术道路上走得更远,就要更加深入地理解。
关于 TCP 三次握手和四次挥手分析文章,网上有太多了,为了减少干扰,
我们首先应该阅读理解一个权威的材料,这里的推荐便是《TCP/IP详解 卷1:协议》第 18 章 TCP 连接的建立与终止。   上面的材料阅读理解完之后,再用 Wireshark 实际抓包分析一下来加深理解。
最后再看看网上其他人的分析。
(有些概念和问题可能之前理解得有问题,需要不断地重复,每次应该会有更深入的理解。)

TCP 三次握手和四次挥手中的一些疑问

关于 TCP 建立连接时的三次握手和和关闭连接时的四次挥手过程细节,我这里就不重复细说了。
上面说的各种资料都有详细说明。
现在我们来提出一些问题。

1. 为什么 TCP 采用 3 次握手而不是 2 次或 4 次 握手?

个人认为知乎的一些比较好理解的回答如下。

原因 1:
双向连接,至少要三次握手。


这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 
而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 
所以三次握手不是 TCP 本身的要求, 而是为了满足"在不可靠信道上可靠地传输信息"这一需求所导致的. 
请注意这里的本质需求,信道不可靠, 数据传输要可靠. 
三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了. 


前提 1: TCP 协议要保证双方可以通信,
即:发送端接收端要确认自己发送的信息对方能接收到,对方发送的信息自己能接收到
前提 2: 在前提一的情况下发送的越少越好

假设 1: 一次握手 即发送端向接受端发送一个包
结论: 发送端无法确认自己发送的信息对方是否收到

假设 2: 两次握手 发送端 发一个包 接收端回一个包
结论:发送端可以确定自己发送的信息能对方能收到 也能确定对方发的包自己能收到 
但接收端只能确定 对方发的包自己能收到 无法确定 自己发的包对方能收到

假设3: 三次握手 发送端 发一个 接收端回一个 发送端接收到之后再发一个
结论 这样发送端和接收端就能确定双方可以通信了

所以三次是满足要求的最小值

三次握手这个说法不好,其实是双方各一次握手,各一次确认,其中一次握手和确认合并在一起

之所以存在 3-way hanshake 的说法,是因为 TCP 是双向通讯协议,
作为响应一方(Responder) 要想初始化发送通道,必须也进行一轮 SYN + ACK。
由于 SYN ACK 在 TCP 分组头部是两个标识位,因此处于优化目的被合并了。
所以达到双方都能进行收发的状态只需要 3 个分组。

所以实际上理解成两次(单向通讯)和四次(不考虑合并)也未尝不可。

原则上任何数据传输都无法确保绝对可靠,三次握手只是确保可靠的基本需要。
双方都需要确认自己的发信和收信功能正常,收信功能通过接收对方信息得到确认,发信功能需要发出信息—>对方回复信息得到确认。
需要第三次握手的原因在于 Server 端在第二次握手(发出信息)后并不知道对方是否能够接收、己方的发送功能是否正常。
但此时数据的单向通道已经建立,对于 Client 来说,已经确认了 Server 端可以接收信号,因此可以单向给 Server 发送数据了。

原因 2:
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
如果两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文, 这时服务器就会建立连接, 浪费掉许多服务器的资源。


在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。
在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。
这两种不同的表述其实阐明的是同一个问题。

谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:
Client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 Server。
本来这是一个早已失效的报文段。
但 Server 收到此失效的连接请求报文段后,就误认为是 Client 再次发出的一个新的连接请求。
于是就向 Client 发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要 Server 发出确认,新的连接就建立了。
由于现在 Client 并没有发出建立连接的请求,因此不会理睬 Server 的确认,也不会向 Server 发送数据。
但 Server 却以为新的传输连接已经建立,并一直等待 Client 发来数据。
这样,Server 的很多资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生。
例如刚才那种情况,Client 不会向 Server 的确认发出确认。Server 由于收不到确认,就知道 Client 并没有要求建立连接。

2. 为什么 TCP 建立连接是三次握手,而关闭连接却是四次挥手呢?

原因:保证 TCP 协议的全双工连接能够可靠关闭

这个我们可以看到和 TCP 建立连接时的原因相同,本质是一样,因为 TCP 是全双工通信,   只不过三次握手时的 SYN + ACK 是放在一个报文中了,而四次挥手时,己方 ACK 和 FIN 报文是分开发送的。


这是因为服务端在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。
而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,
己方也未必全部数据都发送给对方了,所以己方可以立即 close,也可以发送一些数据给对方后,
再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送。

3. 为什么在 TCP 四次挥手中主动关闭的一端 TIME-WAIT 状态必须 等待 2MSL 的时间?

原因有二:

  • 一、保证 TCP 协议的全双工连接能够可靠关闭
  • 二、保证这次连接的重复数据段从网络中消失

先说第一点,如果 Client 直接 CLOSED 了,那么由于 IP 协议的不可靠性或者是其它网络原因,导致 Server 没有收到 Client 最后回复的 ACK。
那么 Server 就会在超时之后继续发送 FIN,此时由于 Client 已经 CLOSED 了,就找不到与重发的 FIN 对应的连接,
最后 Server 就会收到 RST 而不是 ACK,Server 就会以为是连接错误把问题报告给高层。
这样的情况虽然不会造成数据丢失,但是却导致 TCP 协议不符合可靠连接的要求。
所以,Client 不是直接进入 CLOSED,而是要保持 TIME_WAIT,当再次收到 FIN 的时候,能够保证对方收到 ACK,最后正确的关闭连接。

再说第二点,如果 Client 直接 CLOSED,然后又再向 Server 发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。
也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,
但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,
如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达 Server,
由于新连接和老连接的端口号是一样的,又因为 TCP 协议判断不同连接的依据是 socket pair,
于是,TCP 协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。
所以 TCP 连接还要在 TIME_WAIT 状态等待 2 倍 MSL,这样可以保证本次连接的所有数据都从网络中消失。

Ref

《TCP/IP详解 卷1:协议》第18章 TCP连接的建立与终止
RFC 793 - Transmission Control Protocol - IETF Tools
TCP Connection Establishment Process: The “Three-Way Handshake”
TCP Connection Termination
结合Wireshark捕获分组深入理解TCP/IP协议栈之TCP协议(TCP报文格式+三次握手实例)
TCP三次握手和四次挥手
TCP 为什么是三次握手,为什么不是两次或四次?
TCP四次分手中,主动关闭方最后为什么要等待2MSL之后才关闭连接?
TCP的三次握手(建立连接)和四次挥手(关闭连接)
简析TCP的三次握手与四次分手
wireshark抓包图解 TCP三次握手/四次挥手详解