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

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 连接释放相关页面。
http://www.jsqmd.com/news/1205874/

相关文章:

  • KV260视觉套件与ResNet50部署实战指南
  • ELF 1开发板4G模块集成问题排查指南
  • MySQL错误码解析与处理实战指南
  • Nemotron-3-Embed-8B-BF16训练数据集揭秘:5000万+样本的构建过程
  • 萧邦中国官方售后服务中心|地址与客服服务热线权威信息公示(2026年7月更新) - 萧邦中国官方服务中心
  • 揭秘兰州大学智能计算研究中心网络安全项目:openeuler/lzu-icc-nsg全方位技术解析
  • 优化MacBook睡眠功耗:关闭蓝牙/Wi-Fi的自动化方案
  • 2026年国内园林外墙砖厂家 质量适配难 多维度精选排行 - 热点速览
  • 从Prompt Engineering到MCP:AI工具调用的自动化演进
  • RS485接口电路设计挑战与优化方案
  • DeepSeek-OCR Client高级技巧:10个提升OCR识别准确率的方法
  • Hono OpenAPI实战教程:构建带自动文档的RESTful API
  • Relude性能优化秘籍:为什么Text比String更快?
  • Codex多端协同原理与API密钥注入实战指南
  • 天梭中国官方售后服务中心|地址与客户服务热线权威信息声明(2026年7月最新) - 天梭服务中心
  • Ethereum Security Toolbox部署指南:从本地开发到生产环境的完整配置
  • mlx-community/diffusiongemma-26B-A4B-it-nvfp4常见问题解答:新手必知的8个关键知识点
  • CANN Ascend C TPipe事件ID释放
  • oeDevPlugin 高级技巧:10 个提升 openEuler 开发效率的隐藏功能
  • intel-ice开发者入门指南:如何为Intel网络驱动贡献代码的完整教程
  • Windows系统优化神器:Chris Titus Tech‘s Windows Utility详解
  • 帝舵官方售后服务中心地址与客服电话实地考察报告_多信源验证(2026年7月最新) - 帝舵中国官方服务中心
  • 5分钟快速入门 ebusd:从安装到配置的完整教程
  • Windows下WSL+TeX Live+VS Code LaTeX环境零折腾配置指南
  • Windows手动下载安装配置uv 0.11.29配置国内源含安装包和安装教程
  • LLM驱动的代理系统设计与工程实践
  • 【软考备考】存储系统详解:Cache 命中率、三种地址映射,计组最后一座大山(附 10 道练习)
  • VSCode+STM32CubeMX搭建FreeRTOS嵌入式开发环境实战
  • 石家庄本地记账公司:选对伙伴,企业财务轻松又省心 - 热点速览
  • CANN asc-devkit GetCurBufSize函数文档