news 2026/7/17 9:38:50

TCP 四次挥手完整解析:报文、序号、状态与 TIME_WAIT

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TCP 四次挥手完整解析:报文、序号、状态与 TIME_WAIT

文章目录

  • TCP 四次挥手完整解析:报文、序号、状态与 TIME_WAIT
    • 一、先给出最核心的结论
    • 二、分析前的序号约定
      • 两条序号规则
    • 三、第一次挥手:主动关闭方发送 FIN
      • 1. 报文方向
      • 2. 典型首部字段
      • 3. 状态变化
      • 4. FIN 的真实含义
    • 四、第二次挥手:被动关闭方确认 FIN
      • 1. 报文方向
      • 2. 典型首部字段
      • 3. 状态变化
      • 4. 此时形成半关闭状态
      • 5. CLOSE_WAIT 与应用程序的关系
    • 五、第三次挥手:被动关闭方发送自己的 FIN
      • 1. 报文方向
      • 2. 典型首部字段
      • 3. 状态变化
    • 六、第四次挥手:主动关闭方发送最终 ACK
      • 1. 报文方向
      • 2. 典型首部字段
      • 3. 状态变化
    • 七、为什么主动关闭方要进入 TIME_WAIT
      • 原因一:保证最后一个 ACK 可以被补发
      • 原因二:让旧连接的重复报文在网络中消失
      • 不要把 2MSL 固定理解成“四分钟”
    • 八、为什么通常是四次,而建立连接是三次
    • 九、状态迁移总表
      • 状态名称的直观理解
    • 十、丢包和重传时会发生什么
      • 1. 第一次 FIN 丢失
      • 2. 第二次 ACK 丢失
      • 3. 第三次 FIN 丢失
      • 4. 第四次 ACK 丢失
    • 十一、同时关闭:双方都主动发送 FIN
    • 十二、RST 不是四次挥手
    • 十三、与 Linux Socket API 的对应关系
      • 主动关闭方
      • 被动关闭方
    • 十四、常见错误理解
      • 错误 1:客户端一定是主动关闭方
      • 错误 2:收到 FIN 后不能再发送数据
      • 错误 3:FIN 报文不能携带数据
      • 错误 4:ACK 也会消耗一个序号
      • 错误 5:TIME_WAIT 一定出现在客户端
      • 错误 6:看到很多 TIME_WAIT 就说明程序泄漏
      • 错误 7:看到很多 CLOSE_WAIT 是网络太慢
    • 十五、一句话记忆
    • 参考资料

TCP 四次挥手完整解析:报文、序号、状态与 TIME_WAIT

本文以“一端主动关闭、另一端被动关闭”的正常释放流程为主线。配图截取自所提供课件《第5章 运输层.pptx》的 TCP 连接释放部分,并结合 RFC 9293 对序号、半关闭、状态迁移和异常情况进行补充修正。


一、先给出最核心的结论

TCP 是全双工字节流协议。一条 TCP 连接中实际上存在两个相互独立的数据传输方向:

主机 A ───────────────→ 主机 B 主机 A ←─────────────── 主机 B

关闭其中一个方向,并不代表另一个方向也已经关闭。因此,正常释放连接时通常需要:

  1. 主动关闭方发送FIN,表示“我没有更多数据要发送”;
  2. 被动关闭方发送ACK,确认已经收到这个FIN
  3. 被动关闭方处理完剩余数据后,也发送自己的FIN
  4. 主动关闭方发送最后一个ACK

这就是通常所说的TCP 四次挥手

需要注意:四次挥手是典型的正常关闭过程,不表示网络上任何一次 TCP 关闭都一定恰好出现四个独立报文段。如果第二次的ACK与第三次的FIN被合并发送,抓包中可能只看到三个报文段;如果报文丢失并发生重传,则可能看到五个甚至更多报文段。


二、分析前的序号约定

假设连接已经建立,主机 A 主动关闭,主机 B 被动关闭:

  • A 当前下一个可用发送序号为u
  • A 当前期望从 B 收到的下一个字节序号为v
  • B 在半关闭期间可能继续发送数据,因此 B 最终发送FIN时的序号记为w

在不携带数据的典型情况下,四个报文段可写成:

A B │ │ │ FIN=1, ACK=1, seq=u, ack=v │ │────────────────────────────────────────────────────>│ │ │ │ ACK=1, seq=v, ack=u+1 │ │<────────────────────────────────────────────────────│ │ │ │ B 仍然可以继续向 A 发送剩余数据 │ │<────────────────────────────────────────────────────│ │ │ │ FIN=1, ACK=1, seq=w, ack=u+1 │ │<────────────────────────────────────────────────────│ │ │ │ ACK=1, seq=u+1, ack=w+1 │ │────────────────────────────────────────────────────>│ │ │

两条序号规则

  1. FIN会占用一个序号。
    因此,对seq=uFIN进行确认时,确认号是u+1

  2. ACK不占用序号。
    第二次和第四次挥手若不携带数据,其发送序号不会因为发送 ACK 而增加。

RFC 9293 将SYNFIN都计入 TCP 序号空间。FIN在逻辑上位于该报文段最后一个数据字节之后,因此FIN也可以和数据放在同一个 TCP 报文段中。


三、第一次挥手:主动关闭方发送 FIN

1. 报文方向

主机 A ───────── FIN, ACK ─────────> 主机 B

2. 典型首部字段

FIN = 1 ACK = 1 seq = u ack = v

含义如下:

  • FIN=1:A 表示自己已经没有更多数据需要发送;
  • ACK=1:连接已经处于已建立阶段,确认号字段有效;
  • seq=u:该 FIN 使用 A 当前下一个发送序号;
  • ack=v:A 仍然确认自己已经正确收到 B 在v之前的全部字节。

3. 状态变化

A:ESTABLISHED → FIN-WAIT-1 B:仍处于 ESTABLISHED

A 进入FIN-WAIT-1后,等待 B 对该 FIN 的确认。

4. FIN 的真实含义

FIN表示:

本端在这个发送方向上不会再发送新的字节。

它并不表示:

  • 本端不能继续接收数据;
  • 整条 TCP 连接已经立即消失;
  • 对端必须立刻发送自己的 FIN;
  • 当前 FIN 报文段不能携带数据。

如果 A 的 FIN 报文段还携带了N字节数据,且第一个数据字节序号为u,那么 B 对其完整确认时应发送:

ack = u + N + 1

其中N来自数据,额外的1来自 FIN。


四、第二次挥手:被动关闭方确认 FIN

1. 报文方向

主机 A <──────────── ACK ─────────── 主机 B

2. 典型首部字段

ACK = 1 seq = v ack = u + 1

其中:

  • ack=u+1表示 B 已经收到 A 的 FIN;
  • seq=v表示该 ACK 自身不消耗序号,B 当前下一个发送序号仍然是v,除非同时携带数据。

3. 状态变化

B 收到 FIN 并发送 ACK 后:

B:ESTABLISHED → CLOSE-WAIT

A 收到该 ACK 后:

A:FIN-WAIT-1 → FIN-WAIT-2

4. 此时形成半关闭状态

此时:

A → B:已经关闭 B → A:仍然可以传输数据

A 已经承诺不再发送数据,但仍然必须继续接收 B 发来的数据。B 的应用程序也可能还有响应、日志、文件尾部或协议结束信息尚未发送。

5. CLOSE_WAIT 与应用程序的关系

当 B 的 TCP 协议栈收到 A 的 FIN 后,B 端应用程序通常会在recv()read()中得到返回值0,表示:

对端已经完成有序关闭,接收方向到达 EOF。

此时内核已经发送 ACK,连接进入CLOSE-WAIT。接下来何时发送 B 自己的 FIN,取决于B 端应用程序何时调用close()shutdown(..., SHUT_WR)

因此,大量连接长期停留在CLOSE-WAIT,通常说明应用程序收到了对端关闭通知,却没有及时释放自己的套接字。这通常是应用层资源管理问题,而不是 TCP 协议没有发送报文。


五、第三次挥手:被动关闭方发送自己的 FIN

当 B 已经发送完剩余数据,并且应用程序决定关闭自己的发送方向时,B 发送 FIN。

1. 报文方向

主机 A <──────── FIN, ACK ────────── 主机 B

2. 典型首部字段

FIN = 1 ACK = 1 seq = w ack = u + 1

其中:

  • seq=w:B 当前下一个可用的发送序号;
  • w不一定等于第二次挥手时的v,因为 B 在半关闭期间可能又发送了数据;
  • ack=u+1:继续确认 A 的 FIN 以及此前收到的数据。

3. 状态变化

B:CLOSE-WAIT → LAST-ACK A:仍处于 FIN-WAIT-2

B 进入LAST-ACK,等待 A 对 B 的 FIN 进行最终确认。


六、第四次挥手:主动关闭方发送最终 ACK

1. 报文方向

主机 A ───────────── ACK ───────────> 主机 B

2. 典型首部字段

ACK = 1 seq = u + 1 ack = w + 1

其中:

  • seq=u+1:A 的第一个 FIN 已经占用了序号u
  • ack=w+1:B 的 FIN 占用了序号w,所以确认号增加 1。

3. 状态变化

A 发送最终 ACK 后:

A:FIN-WAIT-2 → TIME-WAIT

B 收到最终 ACK 后:

B:LAST-ACK → CLOSED

A 并不会立即进入CLOSED,而是先在TIME-WAIT中停留一段时间,然后才删除该连接的控制信息。


七、为什么主动关闭方要进入 TIME_WAIT

RFC 9293 规定,主动关闭连接的一端通常需要在TIME-WAIT状态停留2 × MSL。MSL 是 Maximum Segment Lifetime,即一个 TCP 报文段在网络中可能存在的最长时间。

TIME-WAIT主要解决两个问题。

原因一:保证最后一个 ACK 可以被补发

TCP 的纯 ACK 报文通常不会单独启动重传计时器。假设第四次挥手的 ACK 丢失:

  1. B 一直处于LAST-ACK,没有收到对自己 FIN 的确认;
  2. B 的 FIN 重传计时器到期;
  3. B 重新发送 FIN;
  4. A 如果仍在TIME-WAIT,就能再次发送 ACK;
  5. B 收到 ACK 后进入CLOSED

如果 A 发送最后一个 ACK 后立即删除连接状态,那么 B 重传 FIN 时,A 可能无法再按照原连接的序号状态正确处理它。

原因二:让旧连接的重复报文在网络中消失

TCP 连接由以下四元组标识:

源 IP、源端口、目的 IP、目的端口

如果旧连接刚刚关闭,同一个四元组立刻建立新连接,而旧连接中的延迟或重复报文仍滞留在网络中,这些旧报文可能干扰新连接。

保留TIME-WAIT可以为旧报文提供足够的自然消亡时间,降低旧连接报文进入新连接实例的风险。

不要把 2MSL 固定理解成“四分钟”

早期 TCP 规范将 MSL 任意定义为 2 分钟,因此教材中常得到2MSL = 4 分钟。但实际操作系统的具体等待时间和连接复用策略可能不同。学习协议时应记住的是:

TIME-WAIT 的协议语义是等待 2MSL, 而不是所有系统都固定等待四分钟。

八、为什么通常是四次,而建立连接是三次

三次握手建立连接时,服务器可以把:

确认客户端 SYN 的 ACK + 服务器自己的 SYN

合并成一个SYN+ACK报文段。

但释放连接时,被动关闭方收到 FIN 后,内核应尽快确认;与此同时,被动关闭方的应用程序可能还有数据尚未发送,暂时不能发送自己的 FIN。因此:

对第一个 FIN 的 ACK

与:

被动关闭方自己的 FIN

通常分开发送,形成四个报文段。

但如果 B 收到 A 的 FIN 时已经没有任何数据需要发送,并且应用程序也立即关闭,B 可以把 ACK 与自己的 FIN 合并成一个FIN+ACK。此时抓包中可能看到:

A → B:FIN,ACK B → A:FIN,ACK A → B:ACK

因此,准确表述应是:

TCP 的正常关闭包含两个方向各自的 FIN/ACK 过程,典型情况下表现为四个报文段。


九、状态迁移总表

阶段主动关闭方 A被动关闭方 B报文
初始ESTABLISHEDESTABLISHED双方正常传输
第一次挥手FIN-WAIT-1ESTABLISHEDA → B:FIN,ACK
第二次挥手完成FIN-WAIT-2CLOSE-WAITB → A:ACK
第三次挥手FIN-WAIT-2LAST-ACKB → A:FIN,ACK
第四次挥手TIME-WAITCLOSEDA → B:ACK
等待结束CLOSEDCLOSEDA 等待2MSL后关闭

状态名称的直观理解

  • FIN-WAIT-1:已经发送 FIN,等待该 FIN 被确认;
  • FIN-WAIT-2:自己的 FIN 已被确认,等待对端发送 FIN;
  • CLOSE-WAIT:已经收到对端 FIN,等待本地应用程序关闭;
  • LAST-ACK:已经发送本端最后一个 FIN,等待最终 ACK;
  • TIME-WAIT:已经发送最终 ACK,保留连接状态以处理重传和旧报文;
  • CLOSED:连接控制块已经被删除。

十、丢包和重传时会发生什么

1. 第一次 FIN 丢失

A 在FIN-WAIT-1中等待确认。重传计时器到期后,A 重新发送 FIN。

2. 第二次 ACK 丢失

A 没有收到对 FIN 的确认,会重传 FIN。B 收到重复 FIN 后,再次发送 ACK。

3. 第三次 FIN 丢失

B 在LAST-ACK中等待最终 ACK。计时器到期后,B 重传 FIN。

4. 第四次 ACK 丢失

B 重传 FIN;A 在TIME-WAIT中再次回复 ACK,并按照实现规则维持或重新开始等待时间。

因此,“四次挥手”描述的是逻辑阶段,并不是说整个网络过程中只能发送四个 TCP 报文段。


十一、同时关闭:双方都主动发送 FIN

如果 A 和 B 几乎同时调用关闭操作,双方的 FIN 可能在网络中交叉:

A:ESTABLISHED → FIN-WAIT-1 B:ESTABLISHED → FIN-WAIT-1

双方在尚未收到自己 FIN 的确认前先收到了对方 FIN,可能进入:

CLOSING

随后双方分别确认对方 FIN,并进入TIME-WAIT,最后进入CLOSED

这属于simultaneous close(同时关闭),状态迁移与普通主动/被动关闭不同,但本质仍然是两个方向分别发送 FIN 并被确认。


十二、RST 不是四次挥手

TCP 还可以通过RST异常终止连接。RST 的特点是:

  • 不执行正常的双向 FIN 关闭;
  • 通常立即丢弃连接状态;
  • 可能导致尚未读取或尚未确认的数据丢失;
  • 应用程序通常得到“连接被重置”等错误,而不是正常 EOF。

因此:

FIN:有序、正常、可确认的关闭 RST:异常、立即的连接中止

十三、与 Linux Socket API 的对应关系

主动关闭方

// 停止发送新数据,但仍保留接收能力。shutdown(fd,SHUT_WR);// 继续读取对端在半关闭期间发来的剩余数据。charbuf[1024];ssize_tn;while((n=recv(fd,buf,sizeof(buf),0))>0){// 处理对端剩余数据}if(n==0){// 收到对端 FIN,输入方向到达 EOF}close(fd);

shutdown(fd, SHUT_WR)更直观地表达 TCP 半关闭:本端不再发送,但仍可接收。

被动关闭方

charbuf[1024];ssize_tn;while((n=recv(fd,buf,sizeof(buf),0))>0){// 处理收到的数据}if(n==0){// 对端已发送 FIN。// 此时协议栈已确认 FIN,连接处于 CLOSE-WAIT。// 如有必要,仍可以发送剩余响应数据。send(fd,"final response",14,0);// 本地应用处理完成后关闭,触发本端 FIN。close(fd);}

需要注意,用户态系统调用与实际报文发送时刻之间由内核 TCP 协议栈协调;例如,内核可能先发送缓存中的数据,再发送 FIN。


十四、常见错误理解

错误 1:客户端一定是主动关闭方

不正确。客户端或服务器都可以主动关闭。谁先发送第一个 FIN,谁就是此次关闭过程中的主动关闭方。

错误 2:收到 FIN 后不能再发送数据

不正确。收到 FIN 只表示对端不会再发送数据。本端仍可在另一个方向继续发送,直到自己也发送 FIN。

错误 3:FIN 报文不能携带数据

不正确。FIN 可以和最后一批数据放在同一报文段中;FIN 在序号空间中位于这些数据之后。

错误 4:ACK 也会消耗一个序号

不正确。纯 ACK 不消耗序号;SYNFIN各占用一个序号,数据按字节占用序号。

错误 5:TIME_WAIT 一定出现在客户端

不正确。通常是主动关闭并发送最后 ACK 的一端进入TIME-WAIT。服务器同样可能主动关闭,因此服务器也可能出现大量TIME-WAIT

错误 6:看到很多 TIME_WAIT 就说明程序泄漏

不一定。TIME-WAIT是 TCP 正常可靠关闭机制的一部分。是否异常,需要结合连接建立速率、端口使用、持续时间和业务模型分析。

错误 7:看到很多 CLOSE_WAIT 是网络太慢

通常不是。大量长期CLOSE-WAIT更常见的原因是应用程序没有在收到 EOF 后及时调用close(),或者工作线程被阻塞、异常路径遗漏了资源释放。


十五、一句话记忆

A 说:我不再发了。 FIN B 说:知道了,但我可能还要发。 ACK B 说:我也发完了。 FIN A 说:知道了,我再等一会儿。 ACK + TIME_WAIT

更严格地说:

TCP 关闭不是“关闭一根管道”,而是分别关闭全双工连接的两个单向字节流;两个 FIN 分别终止两个发送方向,两个 ACK 分别确认这两个 FIN。


参考资料

  1. RFC 9293 — Transmission Control Protocol (TCP)
    https://www.rfc-editor.org/rfc/rfc9293.html
  2. RFC 1337 — TIME-WAIT Assassination Hazards in TCP
    https://www.rfc-editor.org/rfc/rfc1337.html
  3. 用户提供课件:《第5章 运输层.pptx》,TCP 连接释放相关页面。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 9:36:41

HStreamDB完全指南:现代实时数据栈的终极选择

HStreamDB完全指南&#xff1a;现代实时数据栈的终极选择 【免费下载链接】hstream HStreamDB is an open-source, cloud-native streaming database for IoT and beyond. Modernize your data stack for real-time applications. 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/7/17 9:36:36

终极指南:如何为Ternary-Bonsai-27B-gguf开发自定义应用和集成

终极指南&#xff1a;如何为Ternary-Bonsai-27B-gguf开发自定义应用和集成 【免费下载链接】Ternary-Bonsai-27B-gguf 项目地址: https://ai.gitcode.com/hf_mirrors/prism-ml/Ternary-Bonsai-27B-gguf 想要在您的应用中集成强大的27B参数语言模型吗&#xff1f;Ternar…

作者头像 李华
网站建设 2026/7/17 9:33:50

告别真实文件操作!Pyfakefs让Python测试效率提升10倍的秘诀

告别真实文件操作&#xff01;Pyfakefs让Python测试效率提升10倍的秘诀 【免费下载链接】pyfakefs Provides a fake file system that mocks the Python file system modules. 项目地址: https://gitcode.com/gh_mirrors/py/pyfakefs 想要提升Python测试效率&#xff1f…

作者头像 李华
网站建设 2026/7/17 9:33:34

Claude 3超长文档处理与Prompt设计实战指南

1. 超长文档处理的挑战与Claude特性解析 处理超长文档时&#xff0c;我们面临三个核心难题&#xff1a;上下文窗口限制、关键信息分散和语义连贯性保持。以Claude 3系列模型为例&#xff0c;虽然其上下文窗口已扩展至200K tokens&#xff08;约15万英文单词&#xff09;&#x…

作者头像 李华
网站建设 2026/7/17 9:33:32

DeepCompressor量化原理:从低秩分解到渐进量化的技术演进

DeepCompressor量化原理&#xff1a;从低秩分解到渐进量化的技术演进 【免费下载链接】deepcompressor Model Compression Toolbox for Large Language Models and Diffusion Models 项目地址: https://gitcode.com/gh_mirrors/de/deepcompressor DeepCompressor作为专注…

作者头像 李华
网站建设 2026/7/17 9:32:46

AI辅助Java单元测试:从20%到80%覆盖率的工程实践

1. 项目缘起与核心痛点 接手一个遗留的老旧项目&#xff0c;就像走进一间堆满杂物的老房子&#xff0c;表面上看功能都还在&#xff0c;但想动任何一块砖&#xff0c;都得担心会不会引起连锁反应&#xff0c;把整个房子搞塌了。我最近就遇到了这么一个典型的“祖传”Java项目&a…

作者头像 李华