关于三次握手和四次挥手
# 关于三次握手和四次挥手
TCP协议非常重要,这里把它的连接和释放整理一下。
为什么需要三次握手和四次挥手。
三次握手和四次挥手————是TCP链接的。
在TCP/IP四层模型中,tcp属于,传输层。
应用层(HTTP/FTP) → 传输层(TCP/UDP) → 网络层(IP) → 网络接口层(物理设备)
# 关于连接(握手)
# 假设一次握手
- 如果采用一次握手,客户端向服务器发送一个请求报文(SYN),然后认为连接已经建立。
- 在这种情况下,客户端并不知道:
- 服务器是否能够接收到客户端的请求。
- 服务器是否准备好接收并发送数据。
# 风险与问题
- 无法确认服务器是否在线并准备好通信
- 一次握手后,客户端假设连接已经建立,但如果服务器因故障或网络问题未收到请求,客户端的操作将失败。
- 例如,客户端发送数据时,发现服务器无响应或数据丢失,导致浪费资源和增加复杂度。
- 无法防止历史数据包的干扰
- 在实际网络中,由于路由延迟或网络异常,可能存在之前失效的连接请求(老旧的SYN包)滞留在网络中。如果采用一次握手,服务器可能错误地认为是新请求,导致建立一个伪连接。
- 双向通信需要确认发送和接收能力
- TCP是双向通信协议,双方都需要确认对方的接收和发送能力。仅通过一次握手,客户端只能确认自己的数据发出去了,但不能确认服务器是否有能力接收和回复数据。
# 假设两次握手
那一次不行,我们加一次,使用两次握手试下,以下是两次握手的大致过程。
第一次握手:客户端发送SYN(同步序列号)报文,表示请求建立连接,并携带初始序列号Seq=x
。
- 客户端进入SYN_SENT状态。
第二次握手:服务器收到SYN后,回复SYN + ACK(确认)报文,表示同意连接,并携带自己的初始序列号Seq=y
。
- 服务器进入SYN_RECEIVED状态。
- 客户端收到这个报文后认为连接已建立,直接进入ESTABLISHED状态。
现在服务器和客户端彼此都发送了一次。且客户端确保了自己的发送和接收能力都没问题,似乎两次就够了。
# 为什么还不够
但实际上,两次握手的设计中存在以下问题:
- 客户端无法确认服务器已收到自己的ACK
- 在两次握手的过程中,客户端没有发送最后的ACK来确认自己已收到服务器的响应(换句话说:服务端无法确定自己能发送成功)。
- 如果服务器没有收到客户端的确认(例如ACK报文在网络中丢失),服务器会一直处于SYN_RECEIVED状态,无法进入ESTABLISHED状态,导致连接状态不一致。也就是说——服务器返回SYN+ACK后直接认为连接建立,但客户端可能未收到SYN+ACK或丢失,导致连接状态不一致(换句话说:服务器以为连接建立,但客户端没有)
- 可能导致服务器资源被浪费
- 在恶意攻击(如SYN洪泛攻击)场景下,攻击者可以伪造大量的SYN请求包。由于没有第三次握手的确认,服务器会分配资源来维护大量的半连接(SYN_RECEIVED状态)。
- 这些半连接永远无法进入完全建立状态(ESTABLISHED),服务器的资源会被耗尽,影响正常服务。
- 不能有效防止旧的连接请求干扰
- 如果有延迟的旧SYN报文(例如网络中滞留的无效数据包)到达服务器,服务器会错误地认为这是新的连接请求,直接建立连接。这种伪连接可能导致数据混乱或错误。
- 双方无法明确通信是否同步
- TCP需要双方确认彼此的接收和发送能力。
- 两次握手中,客户端无法确认服务器是否得到了它的最后一个状态,也无法确保自己和服务器处于同步状态。
# 三次握手
三次握手在两次握手的基础上增加了一个ACK确认步骤:
- 第一次握手:客户端发送SYN,告诉服务器“我要建立连接,并初始化序列号”。
- 第二次握手:服务器发送SYN + ACK,告诉客户端“我收到了你的请求,也准备好了,以下是我的序列号”。
- 第三次握手:客户端发送ACK,告诉服务器“我收到了你的SYN和ACK,现在我们可以开始通信了”。
通过这个过程:
- 确认双向能力:客户端和服务器都知道对方的接收和发送能力正常。
- 防止旧连接干扰:只有双方都确认的连接才能进入ESTABLISHED状态。
- 避免资源浪费:只有在三次握手完成后,服务器才会分配资源,防止SYN洪泛攻击。
三次握手是TCP协议用来建立可靠连接的重要机制,但它只能尽量保证连接的可靠性,并不能做到绝对可靠。
# 局限性
尽管三次握手在设计上解决了很多问题,但它并不能彻底消除所有潜在问题:
- 延迟过大的旧连接包仍可能影响
问题:如果滞留在网络中的旧SYN报文恰好与当前的连接请求的序列号和端口匹配,可能被误认为是有效连接。
解决方法:
- 增加序列号的随机性。
使用时间戳机制验证连接的时效性。
- SYN洪泛攻击
- 问题:攻击者伪造大量SYN请求但不发送第三次握手,导致服务器资源被占用(SYN_RECEIVED状态)。
- 解决方法:
- 使用SYN Cookie:服务器在没有分配资源的情况下验证第三次握手。
- 限制每个IP的半连接数。
- 无法应对报文丢失或恶意拦截
- 问题:如果握手过程中任何报文被丢失或恶意拦截,握手就会失败。
- 解决方法:
- 超时重传机制可以部分缓解丢包问题。
- 在安全需求较高的场景下,使用更高级协议(如TLS)进行加密和身份验证。
- 无法避免后续数据传输中的问题
- 问题:三次握手只是保证连接的建立过程可靠,无法保证后续数据传输的可靠性(如网络拥塞、链路中断等)。
- 解决方法:
- TCP本身通过流量控制、拥塞控制、重传机制等解决传输中的问题。
# 总结
三次握手在设计上有效解决了一次握手和两次握手的问题,主要体现在:
- 双向确认确保通信双方都准备好。
- 避免旧连接或错误连接干扰。
- 解决状态不一致的问题。
服务端跟客户端分别是在那一次告诉对方自己准备好了。
- 服务端:这一信息是通过第二次握手中的 SYN+ACK 报文传递的。因此,实际上服务器是明确地告诉客户端它已准备好通信的
- 客户端:客户端是在第三次握手的时候,表示它准备好了
想想你在跟人通话时,是否都要分别确认下自己说的话,对方能听到。
# 关于-三次握手的扩展
关于三次握手,如果在第三次握手时,如果发生请求报文丢失的情况,会有什么问题,此时为什么不再增加一次握手?
也就是实现“四次握手”。
在TCP三次握手中,第三次握手的请求报文(客户端发送的ACK)如果丢失,确实会对连接建立过程产生影响,但TCP已经有机制来处理这种情况,而无需增加额外的握手步骤。以下是对问题的分析和处理方式:
流程描述
- 在第二次握手中,服务器发送了 SYN+ACK 报文。
- 客户端接收到这个报文后,发送 ACK(第三次握手)确认,但如果这个 ACK 报文在传输过程中丢失,服务器会认为客户端没有响应。
结果
- 服务器的状态保持在 SYN_RECEIVED:
- 服务器会等待客户端的ACK确认进入 ESTABLISHED 状态。
- 如果没有收到ACK,服务器会在超时时间内重发 SYN+ACK,尝试再次让客户端发送ACK(这是解决方案)。
- 客户端的状态已是 ESTABLISHED:
- 因为客户端已经发送了ACK,并认为连接成功,可能开始尝试发送数据。
潜在问题
- 状态不一致:客户端以为连接已经建立,而服务器还在等待ACK,导致服务器丢弃客户端后续发送的数据。
# 不使用四次握手的原因
如果我们在三次握手的基础上再增加一个步骤,让服务器在收到ACK后发送另一个ACK来确认客户端的状态(即 四次握手),可能看似解决了上述问题,但其实并没有必要,原因如下:
三次握手已经足够处理报文丢失
- 服务器有超时重传机制:
- 如果服务器在合理的时间内没有收到第三次握手的ACK,它会重发 SYN+ACK,直到超时或收到ACK。
- 客户端在收到重传的SYN+ACK时会再次发送ACK,确保最终完成握手。
- TCP协议的可靠性保证:
- TCP通过超时重传和确认机制已经能很好地处理报文丢失问题。
- 第三次握手的ACK只是一个小的确认包,重传的成本很低。
增加一次确认是冗余的
- 在三次握手中,双方已经确认了彼此的状态一致性。增加第四次握手只是为了应对极少数报文丢失的情况,但这种情况可以通过重传机制解决,无需引入新的步骤。
- 增加额外的确认会降低效率,尤其是在高时延网络中,会显著增加连接建立的时间。
连接建立延迟和复杂性增加
- 增加一次确认会让握手过程变成“四次握手”,延长连接建立的时间,降低效率。
- 协议的实现和状态管理也会变得更复杂,得不偿失。
# 关于断开连接(挥手)
那为什么断开连接需要4次挥手(进行四次数据包的发送),按照上面的推演法,我们来从一次到四次一次看下:
# 一次挥手
假设TCP只用一次挥手来关闭连接(例如,客户端直接发送一个FIN,服务器直接关闭连接),可能会产生以下问题:
1. 数据丢失
- 如果服务器还有未发送的数据,但客户端直接关闭连接,则这些数据会被丢弃,无法保证通信的完整性。
2. 状态不同步
- TCP的双方可能对连接状态的理解不一致。一方认为连接已经关闭,另一方可能还在尝试通信,从而导致不稳定的行为。
# 两次挥手
那2次挥手是否可行呢,首先客户端说(它需要的数据已发送完毕后),我要结束了,等服务端的数据发送完毕后,服务端也发送结束,然后就断开连接。
使用 两次挥手 来断开连接表面上看起来可行,但实际上不能完全解决 TCP 连接断开时所需的可靠性
和状态一致性
问题。以下是详细分析:
无法确认对方收到关闭请求
- 问题场景:
- 客户端发送
FIN
后,如果这个FIN
报文丢失,服务器根本不知道客户端已经请求关闭(当然,一段时间内,客户端没收到服务端的响应,可能会重发)因此,该问题不存在。
- 客户端发送
无法确认剩余数据是否被接收
- 问题场景:
- 如果服务器在发送完数据后直接发送
FIN
,而客户端可能尚未接收到这些数据包。 - 这时,连接会直接断开,导致数据丢失。
- 如果服务器在发送完数据后直接发送
所以两次挥手的最大问题是,无法保证客户端是否收到最后发送的那部分数据。那我们再加一次挥手试下。
使用三次挥手,实现双方都能知道彼此的消息准确送达。
# 三次挥手
假设 TCP 使用三次挥手,断开连接的过程如下:
- 客户端发送 FIN:
- 客户端通知服务器:“我已经没有数据要发送了,我的发送方向要关闭。”
- 服务器回复 FIN+ACK:
- 服务器确认收到客户端的 FIN(ACK)。
- 同时告诉客户端:“我也没有数据要发送了,我的发送方向也要关闭”(FIN)。
- 客户端发送 ACK:
- 客户端确认服务器的 FIN,连接完全断开。
表面上看,三次挥手可以完成关闭连接的功能,但它在设计上存在重大缺陷。
三次挥手无法完全解决 TCP 的可靠性需求和全双工通信特点,以下是它的主要问题:
# 数据完整性无法保障
- 问题描述:
- 在三次挥手中,服务器在发送 FIN+ACK 时,同时关闭自己的发送方向(FIN)。如果此时服务器还有未发送完的数据,这些数据将丢失。
- 场景:
- 客户端发送 FIN,表示自己不再发送数据,但它仍可能接收服务器的数据。
- 如果服务器直接发送 FIN+ACK,客户端可能认为服务器的数据已发送完毕,提前释放接收缓冲区,从而导致未接收完的数据丢失。
为什么不能等服务器发送完数据,再发送FIN+ACK呢,这样不就解决了数据的完整性了吗?
似乎可行,但这并不能彻底解决问题,因为 FIN 和 ACK 合并为一个报文(FIN+ACK)本身会带来设计上的局限性。
逻辑混乱:FIN 和 ACK 的功能不独立,或者说全双工通信的独立性被破坏
- 问题:
- ACK 的作用是确认客户端的 FIN,而 FIN 的作用是服务器关闭自己的发送方向。将这两者合并到同一个报文(FIN+ACK),会导致两种功能无法独立运作。
- TCP 是全双工协议,关闭发送和接收方向应该是两个独立的动作。用一个报文同时表示“我收到了你的 FIN”和“我也要关闭发送方向”,破坏了这种独立性。
延迟 ACK 的引入,增加丢包风险
在正常的四次挥手中:
- ACK 和 FIN 是分开的,服务器接收到 FIN 后立即发送 ACK 确认,不存在延迟。
但在你提议的情况下:
- 服务器必须延迟发送 ACK 直到数据全部发送完成,这就引入了新的问题:
- 如果在等待期间,客户端因为未收到 ACK 重发 FIN,服务器需要额外逻辑来处理重复的 FIN。
- 如果延迟 ACK 丢失,客户端无法区分是 ACK 丢失还是 FIN 丢失,导致状态处理变得复杂。
报文丢失后的复杂性
- 问题:
- 如果 FIN+ACK 报文丢失,客户端无法明确是哪部分丢失了:
- 是 ACK 丢失了吗?如果是,客户端会重传 FIN。
- 是 FIN 丢失了吗?如果是,服务器需要重传 FIN+ACK。
- 这种情况下,客户端和服务器需要增加额外的逻辑来处理这种不确定性,协议变得更加复杂。
时间竞争问题
提议方案中,服务器延迟发送 ACK 直到数据传输完成,可能会导致客户端的 TIME_WAIT 和服务器的 CLOSE_WAIT 状态出现竞争:
- 客户端的行为:
- 客户端等待 ACK 超时后可能重传 FIN。
- 如果客户端在未接到 ACK 的情况下进入下一步状态,可能导致状态不一致。
- 服务器的行为:
- 如果服务器在发送 ACK+FIN 的同时,客户端已经超时或释放连接资源,可能导致 FIN 丢失。
所以,三次挥手中使用 FIN+ACK 来压缩通信步骤虽然看似减少了开销,但引入了许多隐患,特别是状态管理的复杂性和数据完整性的潜在问题。
如果为了支持 FIN+ACK 合并报文,需要额外复杂的逻辑,反而不如简单的四次挥手。
TCP 是全双工模式(同一时刻可以同时发送和接收)
# 四次挥手
假设客户端主动关闭连接,服务器被动关闭,具体过程如下:
# 第一步:客户端发送 FIN
- 客户端动作:
- 客户端发送一个
FIN
报文,表示它已经没有数据要发送了,准备关闭自己的发送方向。
- 客户端发送一个
- 服务器状态:
- 服务器收到
FIN
后,知道客户端的发送方向已关闭,但此时服务器仍可以继续发送数据。
- 服务器收到
- 报文格式:
- 客户端 → 服务器:
FIN
(客户端关闭发送方向)
- 客户端 → 服务器:
- 客户端状态:进入 FIN_WAIT_1 状态。
# 第二步:服务器发送 ACK
- 服务器动作:
- 服务器收到
FIN
后,立即发送一个ACK
报文,表示已经确认客户端的关闭请求。 - 此时,服务器可能仍有未发送完的数据,因此只发送
ACK
,不发送FIN
。
- 服务器收到
- 客户端状态:
- 客户端收到
ACK
后,知道服务器已经收到自己的关闭请求,但连接还没有完全关闭,因为服务器可能还有数据需要传输。
- 客户端收到
- 报文格式:
- 服务器 → 客户端:
ACK
(确认客户端的 FIN)
- 服务器 → 客户端:
- 服务器状态:进入 CLOSE_WAIT 状态。
- 客户端状态:进入 FIN_WAIT_2 状态。
# 第三步:服务器发送 FIN
- 服务器动作:
- 服务器完成所有数据发送后,向客户端发送一个
FIN
报文,表示它的发送方向也要关闭。 - 此时,服务器的发送方向关闭,但仍保持接收状态,等待客户端的确认。
- 服务器完成所有数据发送后,向客户端发送一个
- 客户端状态:
- 客户端收到服务器的
FIN
后,知道服务器的发送方向也已关闭。
- 客户端收到服务器的
- 报文格式:
- 服务器 → 客户端:
FIN
(服务器关闭发送方向)
- 服务器 → 客户端:
- 服务器状态:进入 LAST_ACK 状态。
- 客户端状态:进入 TIME_WAIT 状态。
# 第四步:客户端发送 ACK
- 客户端动作:
- 客户端收到服务器的
FIN
后,发送一个ACK
报文,表示确认服务器的关闭请求。 - 此时,客户端进入 TIME_WAIT 状态,并开始一个定时器(通常是 2 倍的最大报文生存时间,MSL,约 2 分钟),以确保服务器收到了
ACK
。
- 客户端收到服务器的
- 服务器状态:
- 服务器收到客户端的
ACK
后,立即关闭连接,进入 CLOSED 状态。
- 服务器收到客户端的
- 报文格式:
- 客户端 → 服务器:
ACK
(确认服务器的 FIN)
- 客户端 → 服务器:
- 客户端状态:保持 TIME_WAIT 状态,定时器到期后,进入 CLOSED 状态。
- 服务器状态:进入 CLOSED 状态。
# 核心设计思想
- 发送和接收方向独立:
- TCP 是全双工协议,发送方向和接收方向是独立的。四次挥手允许客户端和服务器各自独立关闭发送方向,确保数据的完整性。
- 分步确认:
- 每一步都有明确的确认过程,减少了状态不一致的风险。
- TIME_WAIT 的意义:
- 客户端在发送最后一个
ACK
后,进入TIME_WAIT
状态,确保服务器的FIN
重传时可以再次确认,避免因报文丢失导致连接未完全关闭。
- 客户端在发送最后一个
# 总结
四次挥手的设计不仅是为了关闭连接,还为以下目标提供了保障:
- 数据完整性:服务器在回复客户端的
FIN
后,可以继续发送剩余数据,确保数据不会丢失。 - 状态一致性:每个阶段都有明确的确认,确保双方在每一步的状态一致。
- 丢包恢复能力:每个报文都可以单独重传,丢失的
FIN
或ACK
都可以被再次发送或确认。