tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Mac

  • 网络

    • 网络的基本介绍
    • 关于三次握手和四次挥手
      • 关于连接(握手)
        • 假设一次握手
        • 风险与问题
        • 假设两次握手
        • 为什么还不够
        • 三次握手
        • 局限性
        • 总结
        • 关于-三次握手的扩展
        • 不使用四次握手的原因
      • 关于断开连接(挥手)
        • 一次挥手
        • 两次挥手
        • 三次挥手
        • 数据完整性无法保障
        • 四次挥手
        • 第一步:客户端发送 FIN
        • 第二步:服务器发送 ACK
        • 第三步:服务器发送 FIN
        • 第四步:客户端发送 ACK
        • 核心设计思想
        • 总结
    • Get和Post
  • 项目构建合集

  • 工具
  • 网络
EffectTang
2024-11-22
目录

关于三次握手和四次挥手

# 关于三次握手和四次挥手

TCP协议非常重要,这里把它的连接和释放整理一下。

为什么需要三次握手和四次挥手。

三次握手和四次挥手————是TCP链接的。

在TCP/IP四层模型中,tcp属于,传输层。

应用层(HTTP/FTP) → 传输层(TCP/UDP) → 网络层(IP) → 网络接口层(物理设备)
1

# 关于连接(握手)

# 假设一次握手

  • 如果采用一次握手,客户端向服务器发送一个请求报文(SYN),然后认为连接已经建立。
  • 在这种情况下,客户端并不知道:
    1. 服务器是否能够接收到客户端的请求。
    2. 服务器是否准备好接收并发送数据。

# 风险与问题

  1. 无法确认服务器是否在线并准备好通信
    • 一次握手后,客户端假设连接已经建立,但如果服务器因故障或网络问题未收到请求,客户端的操作将失败。
    • 例如,客户端发送数据时,发现服务器无响应或数据丢失,导致浪费资源和增加复杂度。
  2. 无法防止历史数据包的干扰
    • 在实际网络中,由于路由延迟或网络异常,可能存在之前失效的连接请求(老旧的SYN包)滞留在网络中。如果采用一次握手,服务器可能错误地认为是新请求,导致建立一个伪连接。
  3. 双向通信需要确认发送和接收能力
    • TCP是双向通信协议,双方都需要确认对方的接收和发送能力。仅通过一次握手,客户端只能确认自己的数据发出去了,但不能确认服务器是否有能力接收和回复数据。

# 假设两次握手

那一次不行,我们加一次,使用两次握手试下,以下是两次握手的大致过程。

第一次握手:客户端发送SYN(同步序列号)报文,表示请求建立连接,并携带初始序列号Seq=x。

  • 客户端进入SYN_SENT状态。

第二次握手:服务器收到SYN后,回复SYN + ACK(确认)报文,表示同意连接,并携带自己的初始序列号Seq=y。

  • 服务器进入SYN_RECEIVED状态。
  • 客户端收到这个报文后认为连接已建立,直接进入ESTABLISHED状态。

现在服务器和客户端彼此都发送了一次。且客户端确保了自己的发送和接收能力都没问题,似乎两次就够了。

# 为什么还不够

但实际上,两次握手的设计中存在以下问题:

  1. 客户端无法确认服务器已收到自己的ACK
  • 在两次握手的过程中,客户端没有发送最后的ACK来确认自己已收到服务器的响应(换句话说:服务端无法确定自己能发送成功)。
  • 如果服务器没有收到客户端的确认(例如ACK报文在网络中丢失),服务器会一直处于SYN_RECEIVED状态,无法进入ESTABLISHED状态,导致连接状态不一致。也就是说——服务器返回SYN+ACK后直接认为连接建立,但客户端可能未收到SYN+ACK或丢失,导致连接状态不一致(换句话说:服务器以为连接建立,但客户端没有)
  1. 可能导致服务器资源被浪费
  • 在恶意攻击(如SYN洪泛攻击)场景下,攻击者可以伪造大量的SYN请求包。由于没有第三次握手的确认,服务器会分配资源来维护大量的半连接(SYN_RECEIVED状态)。
  • 这些半连接永远无法进入完全建立状态(ESTABLISHED),服务器的资源会被耗尽,影响正常服务。
  1. 不能有效防止旧的连接请求干扰
  • 如果有延迟的旧SYN报文(例如网络中滞留的无效数据包)到达服务器,服务器会错误地认为这是新的连接请求,直接建立连接。这种伪连接可能导致数据混乱或错误。
  1. 双方无法明确通信是否同步
  • TCP需要双方确认彼此的接收和发送能力。
  • 两次握手中,客户端无法确认服务器是否得到了它的最后一个状态,也无法确保自己和服务器处于同步状态。

# 三次握手

三次握手在两次握手的基础上增加了一个ACK确认步骤:

  1. 第一次握手:客户端发送SYN,告诉服务器“我要建立连接,并初始化序列号”。
  2. 第二次握手:服务器发送SYN + ACK,告诉客户端“我收到了你的请求,也准备好了,以下是我的序列号”。
  3. 第三次握手:客户端发送ACK,告诉服务器“我收到了你的SYN和ACK,现在我们可以开始通信了”。

通过这个过程:

  • 确认双向能力:客户端和服务器都知道对方的接收和发送能力正常。
  • 防止旧连接干扰:只有双方都确认的连接才能进入ESTABLISHED状态。
  • 避免资源浪费:只有在三次握手完成后,服务器才会分配资源,防止SYN洪泛攻击。

三次握手是TCP协议用来建立可靠连接的重要机制,但它只能尽量保证连接的可靠性,并不能做到绝对可靠。

# 局限性

尽管三次握手在设计上解决了很多问题,但它并不能彻底消除所有潜在问题:

  1. 延迟过大的旧连接包仍可能影响
  • 问题:如果滞留在网络中的旧SYN报文恰好与当前的连接请求的序列号和端口匹配,可能被误认为是有效连接。

  • 解决方法:

    • 增加序列号的随机性。
  • 使用时间戳机制验证连接的时效性。

  1. SYN洪泛攻击
  • 问题:攻击者伪造大量SYN请求但不发送第三次握手,导致服务器资源被占用(SYN_RECEIVED状态)。
  • 解决方法:
    • 使用SYN Cookie:服务器在没有分配资源的情况下验证第三次握手。
    • 限制每个IP的半连接数。
  1. 无法应对报文丢失或恶意拦截
  • 问题:如果握手过程中任何报文被丢失或恶意拦截,握手就会失败。
  • 解决方法:
    • 超时重传机制可以部分缓解丢包问题。
    • 在安全需求较高的场景下,使用更高级协议(如TLS)进行加密和身份验证。
  1. 无法避免后续数据传输中的问题
  • 问题:三次握手只是保证连接的建立过程可靠,无法保证后续数据传输的可靠性(如网络拥塞、链路中断等)。
  • 解决方法:
    • TCP本身通过流量控制、拥塞控制、重传机制等解决传输中的问题。

# 总结

三次握手在设计上有效解决了一次握手和两次握手的问题,主要体现在:

  1. 双向确认确保通信双方都准备好。
  2. 避免旧连接或错误连接干扰。
  3. 解决状态不一致的问题。

服务端跟客户端分别是在那一次告诉对方自己准备好了。

  • 服务端:这一信息是通过第二次握手中的 SYN+ACK 报文传递的。因此,实际上服务器是明确地告诉客户端它已准备好通信的
  • 客户端:客户端是在第三次握手的时候,表示它准备好了

想想你在跟人通话时,是否都要分别确认下自己说的话,对方能听到。

# 关于-三次握手的扩展

关于三次握手,如果在第三次握手时,如果发生请求报文丢失的情况,会有什么问题,此时为什么不再增加一次握手?

也就是实现“四次握手”。

在TCP三次握手中,第三次握手的请求报文(客户端发送的ACK)如果丢失,确实会对连接建立过程产生影响,但TCP已经有机制来处理这种情况,而无需增加额外的握手步骤。以下是对问题的分析和处理方式:

流程描述

  • 在第二次握手中,服务器发送了 SYN+ACK 报文。
  • 客户端接收到这个报文后,发送 ACK(第三次握手)确认,但如果这个 ACK 报文在传输过程中丢失,服务器会认为客户端没有响应。

结果

  • 服务器的状态保持在 SYN_RECEIVED:
    • 服务器会等待客户端的ACK确认进入 ESTABLISHED 状态。
    • 如果没有收到ACK,服务器会在超时时间内重发 SYN+ACK,尝试再次让客户端发送ACK(这是解决方案)。
  • 客户端的状态已是 ESTABLISHED:
    • 因为客户端已经发送了ACK,并认为连接成功,可能开始尝试发送数据。

潜在问题

  • 状态不一致:客户端以为连接已经建立,而服务器还在等待ACK,导致服务器丢弃客户端后续发送的数据。

# 不使用四次握手的原因

如果我们在三次握手的基础上再增加一个步骤,让服务器在收到ACK后发送另一个ACK来确认客户端的状态(即 四次握手),可能看似解决了上述问题,但其实并没有必要,原因如下:

三次握手已经足够处理报文丢失

  1. 服务器有超时重传机制:
    • 如果服务器在合理的时间内没有收到第三次握手的ACK,它会重发 SYN+ACK,直到超时或收到ACK。
    • 客户端在收到重传的SYN+ACK时会再次发送ACK,确保最终完成握手。
  2. TCP协议的可靠性保证:
    • TCP通过超时重传和确认机制已经能很好地处理报文丢失问题。
    • 第三次握手的ACK只是一个小的确认包,重传的成本很低。

增加一次确认是冗余的

  • 在三次握手中,双方已经确认了彼此的状态一致性。增加第四次握手只是为了应对极少数报文丢失的情况,但这种情况可以通过重传机制解决,无需引入新的步骤。
  • 增加额外的确认会降低效率,尤其是在高时延网络中,会显著增加连接建立的时间。

连接建立延迟和复杂性增加

  • 增加一次确认会让握手过程变成“四次握手”,延长连接建立的时间,降低效率。
  • 协议的实现和状态管理也会变得更复杂,得不偿失。

# 关于断开连接(挥手)

那为什么断开连接需要4次挥手(进行四次数据包的发送),按照上面的推演法,我们来从一次到四次一次看下:

# 一次挥手

假设TCP只用一次挥手来关闭连接(例如,客户端直接发送一个FIN,服务器直接关闭连接),可能会产生以下问题:

1. 数据丢失

  • 如果服务器还有未发送的数据,但客户端直接关闭连接,则这些数据会被丢弃,无法保证通信的完整性。

2. 状态不同步

  • TCP的双方可能对连接状态的理解不一致。一方认为连接已经关闭,另一方可能还在尝试通信,从而导致不稳定的行为。

# 两次挥手

那2次挥手是否可行呢,首先客户端说(它需要的数据已发送完毕后),我要结束了,等服务端的数据发送完毕后,服务端也发送结束,然后就断开连接。

使用 两次挥手 来断开连接表面上看起来可行,但实际上不能完全解决 TCP 连接断开时所需的可靠性和状态一致性问题。以下是详细分析:

无法确认对方收到关闭请求

  • 问题场景:
    • 客户端发送 FIN 后,如果这个 FIN 报文丢失,服务器根本不知道客户端已经请求关闭(当然,一段时间内,客户端没收到服务端的响应,可能会重发)因此,该问题不存在。

无法确认剩余数据是否被接收

  • 问题场景:
    • 如果服务器在发送完数据后直接发送 FIN,而客户端可能尚未接收到这些数据包。
    • 这时,连接会直接断开,导致数据丢失。

所以两次挥手的最大问题是,无法保证客户端是否收到最后发送的那部分数据。那我们再加一次挥手试下。

使用三次挥手,实现双方都能知道彼此的消息准确送达。

# 三次挥手

假设 TCP 使用三次挥手,断开连接的过程如下:

  1. 客户端发送 FIN:
    • 客户端通知服务器:“我已经没有数据要发送了,我的发送方向要关闭。”
  2. 服务器回复 FIN+ACK:
    • 服务器确认收到客户端的 FIN(ACK)。
    • 同时告诉客户端:“我也没有数据要发送了,我的发送方向也要关闭”(FIN)。
  3. 客户端发送 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 报文丢失,客户端无法明确是哪部分丢失了:
      1. 是 ACK 丢失了吗?如果是,客户端会重传 FIN。
      2. 是 FIN 丢失了吗?如果是,服务器需要重传 FIN+ACK。
    • 这种情况下,客户端和服务器需要增加额外的逻辑来处理这种不确定性,协议变得更加复杂。

时间竞争问题

提议方案中,服务器延迟发送 ACK 直到数据传输完成,可能会导致客户端的 TIME_WAIT 和服务器的 CLOSE_WAIT 状态出现竞争:

  1. 客户端的行为:
    • 客户端等待 ACK 超时后可能重传 FIN。
    • 如果客户端在未接到 ACK 的情况下进入下一步状态,可能导致状态不一致。
  2. 服务器的行为:
    • 如果服务器在发送 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 状态。

# 核心设计思想

  1. 发送和接收方向独立:
    • TCP 是全双工协议,发送方向和接收方向是独立的。四次挥手允许客户端和服务器各自独立关闭发送方向,确保数据的完整性。
  2. 分步确认:
    • 每一步都有明确的确认过程,减少了状态不一致的风险。
  3. TIME_WAIT 的意义:
    • 客户端在发送最后一个 ACK 后,进入 TIME_WAIT 状态,确保服务器的 FIN 重传时可以再次确认,避免因报文丢失导致连接未完全关闭。

# 总结

四次挥手的设计不仅是为了关闭连接,还为以下目标提供了保障:

  1. 数据完整性:服务器在回复客户端的 FIN 后,可以继续发送剩余数据,确保数据不会丢失。
  2. 状态一致性:每个阶段都有明确的确认,确保双方在每一步的状态一致。
  3. 丢包恢复能力:每个报文都可以单独重传,丢失的 FIN 或 ACK 都可以被再次发送或确认。
上次更新: 2025/04/23, 16:23:16
网络的基本介绍
Get和Post

← 网络的基本介绍 Get和Post→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式