当前位置: 首页 > ds >正文

计算机网络 TCP三次握手、四次挥手超详细流程【报文交换、状态变化】

TCP(传输控制协议)是互联网最重要的协议之一,它保证了数据的可靠、有序传输。连接建立时的“三次握手”和连接关闭时的“四次挥手”是其核心机制,涉及特定的报文交换和状态变化。

一、TCP 三次握手(Three-Way Handshake) - 建立连接

目的:同步双方的初始序列号(Sequence Number,简称 Seq),确认对方能够正常收发数据,建立双向连接。

状态变化详细过程:

  1. CLOSED -> LISTEN(服务器端):
    • 服务器启动,调用 listen() 进入 LISTEN 状态,准备接受连接请求。
  2. SYN_SENT(客户端):
    • 客户端调用 connect() 主动发起连接请求。
    • 客户端发送一个 SYN 报文:
      • SYN 标志位设置为 1
      • 随机生成一个初始序列号 client_seq = x
      • 无确认号(因为还没收到对方的任何序列号)。
    • 客户端进入 SYN_SENT 状态(表示已发送 SYN,等待对方的 SYN+ACK)。
  3. LISTEN -> SYN_RCVD(服务器端):
    • 服务器处于 LISTEN 状态,接收到客户端发来的 SYN 报文。
    • 服务器决定接受连接,并发送一个 SYN + ACK 报文:
      • SYN 标志位设置为 1
      • ACK 标志位设置为 1
      • 随机生成一个初始序列号 server_seq = y
      • 将确认号 ack = x + 1(因为客户端的 SYN 占用了序列号 x,所以服务器期望收到客户端发来的下一个数据字节的序列号是 x+1)。
    • 服务器进入 SYN_RCVD 状态(表示已发送 SYN+ACK,等待客户的 ACK)。
  4. SYN_SENT -> ESTABLISHED(客户端):
    • 客户端处于 SYN_SENT 状态,收到服务器发来的 SYN + ACK 报文。
    • 客户端发送一个 ACK 报文:
      • ACK 标志位设置为 1
      • 序列号 seq = x + 1(因为客户端的 SYN 报文占用了序列号 x,所以下一个报文的序列号是 x+1)。
      • 确认号 ack = y + 1(因为服务器的 SYN 报文占用了序列号 y,所以客户端期望收到服务器发来的下一个数据字节的序列号是 y+1)。
    • 客户端进入 ESTABLISHED 状态(表示连接已建立)。
  5. SYN_RCVD -> ESTABLISHED(服务器端):
    • 服务器处于 SYN_RCVD 状态,收到客户端发来的 ACK 报文。
    • 服务器验证该 ACK 报文的确认号 ack 是否等于 y + 1
    • 验证通过后,服务器也进入 ESTABLISHED 状态(表示连接已建立)。

至此,双向通信通道建立完成。

二、TCP 四次挥手(Four-Way Handshake) - 关闭连接

目的:双方都确认对方不再发送数据,安全地终止双向连接。

状态变化详细过程:

  1. ESTABLISHED -> FIN_WAIT_1(主动关闭方 - 通常是客户端):
    • 主动关闭方(A)调用 close() 或进程退出。
    • A 发送一个 FIN 报文:
      • FIN 标志位设置为 1
      • 序列号 seq = u(等于 A 要发送的下一个数据的序列号,但 FIN 消耗一个序列号)。
    • A 进入 FIN_WAIT_1 状态(表示已发送 FIN,等待对方的 ACK 对方的 FIN)。
  2. ESTABLISHED -> CLOSE_WAIT(被动关闭方):
    • 被动关闭方(B)处于 ESTABLISHED 状态,收到 A 发来的 FIN 报文。
    • B 知道自己该方向的数据发送通道将被关闭(应用程序会收到 EOF)。
    • B 发送一个 ACK 报文:
      • ACK 标志位设置为 1
      • 序列号 seq = v(B 自己的下一个序列号)。
      • 确认号 ack = u + 1(表示收到了 A 的 FIN 报文)。
    • B 进入 CLOSE_WAIT 状态(表示收到对方的 FIN,自己的关闭过程开始,等待自己的应用程序通知关闭)。此时:A 到 B 的发送通道已关闭(A不再发送数据),但 B 到 A 的发送通道可能还有数据需要发送。
  3. FIN_WAIT_1 -> FIN_WAIT_2(主动关闭方):
    • A 处于 FIN_WAIT_1 状态,收到 B 发来的 ACK 报文。
    • A 验证该 ACK 报文的确认号 ack 是否等于 u + 1
    • 验证通过后,A 进入 FIN_WAIT_2 状态(表示已收到对方对 FIN 的 ACK,等待对方发送 FIN)。
  4. CLOSE_WAIT -> LAST_ACK(被动关闭方):
    • B 处于 CLOSE_WAIT 状态,完成了自己方向的数据发送(应用程序调用 close())。
    • B 发送一个 FIN 报文:
      • FIN 标志位设置为 1
      • 序列号 seq = w(这个序列号可能等于 v(如果 CLOSE_WAIT 期间 B 没有发数据)或大于 v(如果 B 发过数据))。
      • 确认号 ack 仍然是 u + 1(因为在这个方向上 A 没有新数据)。
    • B 进入 LAST_ACK 状态(表示自己已发送 FIN,等待对方最后的 ACK)。
  5. FIN_WAIT_2 -> TIME_WAIT(主动关闭方):
    • A 处于 FIN_WAIT_2 状态,收到 B 发来的 FIN 报文。
    • A 发送一个 ACK 报文:
      • ACK 标志位设置为 1
      • 序列号 seq = u + 1(之前的 FIN 报文序列号是 u)。
      • 确认号 ack = w + 1(确认 B 的 FIN 报文)。
    • A 进入 TIME_WAIT 状态(也称为 2MSL Wait 状态)。此状态会持续一段时间(通常为 2 * Maximum Segment Lifetime (MSL),默认在 Linux 中是 60 秒)。
  6. LAST_ACK -> CLOSED(被动关闭方):
    • B 处于 LAST_ACK 状态,收到 A 发来的 ACK 报文。
    • B 验证该 ACK 报文的确认号 ack 是否等于 w + 1
    • 验证通过后,B 关闭连接,进入 CLOSED 状态。
  7. TIME_WAIT -> CLOSED(主动关闭方):
    • A 在 TIME_WAIT 状态等待 2MSL 时间。
    • 目的:
      • 确保最后一个 ACK 报文送达 B: 如果 B 没有收到最后一个 ACK(它在 LAST_ACK 状态等待),会在超时后重发 FIN。处于 TIME_WAIT 的 A 收到这个重发的 FIN,会再次发送 ACK。
      • 确保网络中所有旧的报文段消散: 防止具有相同四元组(源IP、源端口、目的IP、目的端口)的下一个新连接,错误地接收和处理上一个旧连接的残留报文。
    • 2MSL 时间到,A 关闭连接,进入 CLOSED 状态。

至此,连接完全终止。

核心问题解答

为什么需要“三次”握手?两次握手不行吗?

不行。 原因主要有:

  1. 避免失效连接请求造成的资源浪费和历史连接问题:
    • 场景: 假设客户端发送了第一个 SYN=1 请求(X),但因网络拥塞严重延迟。客户端超时未收到响应,会重发第二个 SYN=1 请求(Y)。Y 成功到达,服务器响应 SYN+ACK,客户端也响应 ACK,完成三次握手建立连接。通信结束,连接关闭。
    • 问题: 现在那个延迟了的初始 SYN 请求 X 终于到达了服务器。如果只有两次握手(服务器收到 SYN -> 响应 SYN+ACK -> 建立连接),服务器会误以为这是一个新的连接请求(历史连接),并立即为其分配资源(建立连接表项、分配缓冲区等)。
    • 后果: 服务器资源被无意义占用(因为客户端此时根本不知道还有这个连接存在,也不会发送数据)。此外,如果这个“幽灵连接”被分配了新的序列号,也可能干扰后续真正的新连接(相同端口复用)。
    • **解决:**三次握手中的第三次握手(ACK)就是客户端的明确确认信号。 当那个延迟的 SYN 包 X 到达服务器时,服务器会发送 SYN+ACK,但客户端已经关闭了最初的连接意图,它会忽略这个 SYN+ACK 或者发送 RST 拒绝。这样服务器就不会为这个无效请求建立连接。
  2. 初始序列号同步的双向确认:
    • 通信双方都需要知道对方的初始序列号。两次握手只能确保客户端知道服务器的初始序列号(在服务器的 SYN+ACK 中),并确认服务器的发送能力。但服务器在第二次握手时发送的 SYN+ACK,并未得到客户端的确认(仅两次握手的话,服务器在第二步就认为连接建立了)。
    • 问题: 服务器不知道客户端是否成功收到了自己的序列号 y。如果这个 SYN+ACK 丢失了,服务器认为自己建立了连接(已发送SYN+ACK -> 等待ACK),但客户端还在等待 SYN+ACK(它没收到),双方状态不一致。
    • **解决:**第三次握手的 **ACK** 报文明确告诉服务器:“我收到了你的初始序列号 **y**,我期望的下一个序列号是 **y+1**”。 这完成了服务器序列号的同步确认。

总结: 三次握手是保证可靠性、防止建立错误连接、且完成序列号双向同步确认的最小开销方案。

为什么需要“四次”挥手?

主要原因是:TCP 连接是全双工(Full-Duplex)的。

  1. 双向独立的关闭通道:
    • 当客户端发送 FIN 时,表示它数据发送完毕(不再往服务器写数据),但服务器到客户端方向的数据发送通道并未立即关闭。
    • 服务器收到 FIN 后,知道自己该方向的数据接收通道已关闭(收到EOF),但需要检查自己的应用程序是否还有数据要发送给客户端。如果还有数据要发送(比如最后要确认客户端请求操作成功的消息),服务器会在 CLOSE_WAIT 状态继续发送这些数据。
    • 只有等待服务器自己也确定所有数据都发送完毕(应用层调用 close())时,它才会发送自己的 FIN 来关闭“服务器->客户端”方向的发送通道。
    • 因此,关闭连接需要四个步骤:
      • A -> B 发送 FIN:关闭 A 的发送通道。
      • B -> A 发送 ACK:确认收到 A 的 FIN。此时 B 的接收通道关闭(知道 A 不再发数据)。
      • B -> A 发送 FIN:关闭 B 的发送通道。(这条 FIN 的发送时间独立于前面的 ACK)
      • A -> B 发送 ACK:确认收到 B 的 FIN。
  2. FIN 和 ACK 可能无法合并:
    • 当 B 收到 A 的 FIN 时,它必须立即响应一个 ACK(这是协议要求)。但此时 B 的应用程序可能还未决定关闭/发送完自己的数据(处于 CLOSE_WAIT 状态),因此 B 的 FIN 报文无法像第二次握手(SYN+ACK)那样与此时的 ACK 合并发送。ACK 必须在收到 FIN 后立即发送,而 FIN 必须等 B 准备好关闭其发送端时才发送。

总结: 四次挥手是 TCP 全双工特性决定的。发送 FIN 意味着“我这边不准备再发送数据了”。两次挥手只能关闭一个方向(A->B),四次挥手才能彻底关闭两个独立的数据发送通道(A->B 和 B->A),确保数据完整性。

http://www.xdnf.cn/news/18243.html

相关文章:

  • nn.Module模块介绍
  • USB 2.0声卡
  • 考研复习-操作系统-第一章-计算机系统概述
  • k8s-单主机Master集群部署+单个pod部署lnmp论坛服务(小白的“升级打怪”成长之路)
  • 什么是GD库?PHP中7大类64个GD库函数用法详解
  • 【撸靶笔记】第五关:GET - Double Injection - Single Quotes - String
  • Qt——主窗口 mainWindow
  • GaussDB常用术语缩写及释义
  • 【Golang】:错误处理
  • AI Search进化论:从RAG到DeepSearch的智能体演变全过程
  • 第12章《学以致用》—PowerShell 自学闭环与实战笔记
  • 第七十七章:多模态推理与生成——开启AI“从无到有”的时代!
  • 计算机程序编程软件开发设计之node..js语言开发的基于Vue框架的选课管理系统的设计与实现、基于express框架的在线选课系统的设计与实现
  • Jenkins - CICD 注入环境变量避免明文密码暴露
  • Python中f - 字符串(f-string)
  • Hadoop入门
  • 前端基础知识版本控制系列 - 05( Git 中 HEAD、工作树和索引之间的区别)
  • 图论水题4
  • 写作路上的迷茫与突破
  • java_spring boot 中使用 log4j2 及 自定义layout设置示例
  • NestJS 手动集成TypeORM
  • 关于第一次接触Linux TCP/IP网络相关项目
  • Docker入门:容器化技术的第一堂课
  • python---装饰器
  • 在线编程题目之小试牛刀
  • [每周一更]-(第155期):Go 1.25 发布:新特性、技术思考与 Go vs Rust 竞争格局分析
  • 回溯剪枝的 “减法艺术”:化解超时危机的 “救命稻草”(一)
  • 机器学习算法篇(十三)------词向量转化的算法思想详解与基于词向量转换的文本数据处理的好评差评分类实战(NPL基础实战)
  • 微服务之间的调用需要走网关么?
  • Linux Shell定时检查日期执行Python脚本