欢迎光临
我们一直在努力

优雅告别:TCP四次挥手完整解析

优雅告别:TCP四次挥手完整解析

    • 前言
    • 一、为什么是四次挥手?
    • 二、四次挥手完整流程图
    • 三、每一步的详细拆解
      • 第一步:主动关闭方发送 FIN
      • 第二步:被动关闭方回复 ACK
      • 第三步:被动关闭方发送 FIN
      • 第四步:主动关闭方回复 ACK
    • 四、半关闭状态详解
    • 五、状态流转完整图解
    • 六、TIME_WAIT 为什么存在?
      • 6.1 TIME_WAIT 的两个作用
      • 6.2 2MSL 是多少?
      • 6.3 TIME_WAIT 过多有什么问题?
    • 七、为什么挥手需要四次而不是三次?
    • 八、抓包实战分析
      • 8.1 使用 netcat 模拟连接
      • 8.2 抓包结果
    • 九、常见面试题
      • Q1:四次挥手中哪一次可能丢失?如何应对?
      • Q2:为什么 TIME_WAIT 在主动关闭方,而不是被动关闭方?
      • Q3:服务器产生大量 TIME_WAIT 说明什么?
      • Q4:服务器产生大量 CLOSE_WAIT 说明什么?
      • Q5:为什么握手是三次,挥手是四次?
    • 十、总结速查表

🌺The Begin🌺点点关注,收藏不迷路🌺

⬇ ⬇ 底部 ⬇ ⬇

前言

我们之前详细聊过 TCP 的 三次握手——如何建立连接。但有连接就有断开。

TCP 断开连接的过程,被称为 四次挥手(Four-Way Wavehand)。

相比三次握手的相对简单,四次挥手涉及更多的细节:半关闭状态、TIME_WAIT 的作用、为什么是四次而不是三次……这些都是面试中的高频考点。

今天这篇文章,我们从 挥手流程 → 每一步详解 → 状态变化 → 核心问题解答 → 抓包实战,彻底讲透 TCP 四次挥手。


一、为什么是四次挥手?

先回忆一下:三次握手用了 3 个包建立连接,那断开为什么需要 4 个包?

核心原因:TCP 是全双工的。

  • 每个方向的数据传输是独立的
  • 每一端都需要单独关闭自己的发送通道

换句话说:

  • 客户端说“我发完了”(FIN)
  • 服务器回复“我知道你发完了”(ACK)
  • 服务器说“我也发完了”(FIN)
  • 客户端回复“我知道你发完了”(ACK)

所以,四次挥手 = 两个方向的独立关闭,每个方向需要 1 次 FIN + 1 次 ACK。


二、四次挥手完整流程图

Client (客户端) Server (服务端)
│ │
│ 数据发送完毕 │
│ │
│ 1. FIN (seq=u) │
│ ─────────────────────────────► │
│ │
│ 2. ACK (ack=u+1, seq=v) │
│ ◄───────────────────────────── │
│ │
│ 【半关闭状态】 │
│ 客户端不再发送数据 │
│ 但可以接收数据 │
│ │
│ 3. FIN (seq=w) │
│ ◄───────────────────────────── │
│ │
│ 4. ACK (ack=w+1, seq=u+1) │
│ ─────────────────────────────► │
│ │
│ 等待 2MSL 后完全关闭 │


三、每一步的详细拆解

第一步:主动关闭方发送 FIN

发起方:通常是客户端,但服务器也可以主动关闭

报文内容:

  • FIN = 1(结束位,表示不再发送数据)
  • seq = u(当前序列号)

发生时机:主动关闭方调用 close() 或 shutdown(SHUT_WR)

状态变化:主动关闭方 → FIN_WAIT_1

含义:“我的数据已经发完了,我要关闭连接了。”


第二步:被动关闭方回复 ACK

被动关闭方收到 FIN 后:

  • 回复 ACK = 1
  • ack = u + 1(确认收到 FIN)
  • seq = v(自己的序列号)

状态变化:被动关闭方 → CLOSE_WAIT

状态变化:主动关闭方收到 ACK 后 → FIN_WAIT_2

含义:“我知道你发完了,但我可能还有数据要发(你先等着)。”

此时连接进入 半关闭(Half-Close) 状态:

  • 主动关闭方:不再发送数据,但还能接收数据
  • 被动关闭方:还可以继续发送数据

第三步:被动关闭方发送 FIN

当被动关闭方也发送完所有数据后:

  • 发送 FIN = 1
  • seq = w(可能是 v 加上已发送的数据长度)
  • ack 仍然为 u + 1(可省略)

状态变化:被动关闭方 → LAST_ACK

状态变化:主动关闭方收到 FIN 后 → TIME_WAIT

含义:“我也没有数据要发了,彻底关闭吧。”


第四步:主动关闭方回复 ACK

主动关闭方收到 FIN 后:

  • 发送 ACK = 1
  • ack = w + 1
  • seq = u + 1

状态变化:主动关闭方 → 等待 2MSL 后 → CLOSED

状态变化:被动关闭方收到 ACK 后 → CLOSED

含义:“我知道你也发完了,连接正式关闭。”


四、半关闭状态详解

半关闭(Half-Close) 是 TCP 的一个特性:允许一方关闭自己的发送通道,但仍可以接收对方的数据。

Client Server
│ │
│ FIN ─────────────────────────► │
│ ACK ◄───────────────────────── │
│ │
│ 【半关闭】 │
│ 客户端不能再发数据 │
│ 但可以收数据 │
│ │
│ ◄──────────── 服务器继续发送数据 ────┤
│ │
│ FIN ◄───────────────────────── │
│ ACK ─────────────────────────► │

使用场景:

  • 类似 shutdown(SHUT_WR) 的场景
  • 某些协议(如 HTTP/1.0)可以利用半关闭

五、状态流转完整图解

主动关闭方 被动关闭方
(Client) (Server)

ESTABLISHED ESTABLISHED
│ │
│ 发送 FIN │
▼ │
FIN_WAIT_1 ──────────────────────────► CLOSE_WAIT
│ │
│ 收到 ACK │
▼ │
FIN_WAIT_2 │
│ │
│ ◄─────────── 发送 FIN │
│ ▼
│ LAST_ACK
│ │
│ 发送 ACK │
▼ │
TIME_WAIT │
│ │
│ 等待 2MSL ▼
▼ CLOSED
CLOSED

状态速查表:

状态含义所在端
FIN_WAIT_1 已发送 FIN,等待 ACK 主动关闭方
FIN_WAIT_2 已收到 ACK,等待 FIN 主动关闭方
CLOSE_WAIT 收到 FIN,等待本方关闭 被动关闭方
LAST_ACK 已发送 FIN,等待 ACK 被动关闭方
TIME_WAIT 已发送 ACK,等待 2MSL 主动关闭方
CLOSED 完全关闭 双方

六、TIME_WAIT 为什么存在?

TIME_WAIT 是四次挥手中最容易被问到的问题。

6.1 TIME_WAIT 的两个作用

  • 确保最后的 ACK 能到达对方
  • 如果最后的 ACK 丢失了:
    Client ── FIN ──► Server
    Client ◄── ACK ── Server
    Client ◄── FIN ── Server
    Client ── ACK ──► Server 【这个包丢了】

    如果 Client 立刻关闭:
    Server 会一直等 ACK(LAST_ACK 状态),超时后会重发 FIN

    如果 Client 在 TIME_WAIT 状态:
    – 收到 Server 重发的 FIN
    – 重新发送 ACK

  • 让旧连接的残余包在网络中消失
  • 2MSL = 2 × Maximum Segment Lifetime(报文最大生存时间,通常为 2 分钟)

    保证本次连接的包全部在网络中消亡,避免被新连接误认。

    6.2 2MSL 是多少?

    系统2MSL 默认值
    Linux 60 秒
    Windows 240 秒
    macOS 30 秒

    查看 Linux 配置:

    cat /proc/sys/net/ipv4/tcp_fin_timeout
    # 输出 60(单位:秒,实际是 FIN_WAIT_2 超时,TIME_WAIT 相关参数需看其他配置)

    6.3 TIME_WAIT 过多有什么问题?

    每个 TIME_WAIT 连接会占用一个端口(主动关闭方)。高并发短连接场景下,端口被耗尽可能导致 Cannot assign requested address。

    解决方案:

    # 开启端口重用
    echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

    # 快速回收(Linux 4.12 后被移除)
    # 不推荐,可能导致 NAT 问题

    # 调整本地端口范围
    echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range

    // Java 中开启端口重用
    ServerSocket socket = new ServerSocket();
    socket.setReuseAddress(true);


    七、为什么挥手需要四次而不是三次?

    问题:第二次挥手(ACK)和第三次挥手(FIN)能否合并为一个包?

    答案:通常不能,但有可能。

    在特殊情况下(被动关闭方没有数据要发),确实可以合并:

    Client ── FIN ──► Server
    Client ◄── FIN+ACK ── Server (合并了)
    Client ── ACK ──► Server

    这种情况仍然只有三个包,但通常不称为“三次挥手”,因为这是优化后的特殊情况。

    为什么大多数情况下是四次?

    因为被动关闭方收到 FIN 后:

  • 内核立即回复 ACK(因为收到 FIN 是事件)
  • 但应用程序可能还有数据要发送,不能立即关闭
  • 所以 FIN 要等应用调用 close() 才会发出
  • 这两步在时间上是分离的,所以需要两个包。


    八、抓包实战分析

    8.1 使用 netcat 模拟连接

    # 终端1 – 服务器
    nc -l 8888

    # 终端2 – 客户端
    nc localhost 8888

    按 Ctrl+C 关闭客户端,用 tcpdump 抓包:

    tcpdump -i lo port 8888 -nn -S

    8.2 抓包结果

    1 0.000000 127.0.0.1.54321 → 127.0.0.1.8888 TCP [FIN, ACK] Seq=101 Ack=201
    2 0.000015 127.0.0.1.8888 → 127.0.0.1.54321 TCP [ACK] Seq=201 Ack=102
    3 0.000089 127.0.0.1.8888 → 127.0.0.1.54321 TCP [FIN, ACK] Seq=201 Ack=102
    4 0.000099 127.0.0.1.54321 → 127.0.0.1.8888 TCP [ACK] Seq=102 Ack=202

    分析:

    • 包1:客户端发送 FIN+ACK(seq=101)
    • 包2:服务器回复 ACK(确认收到 FIN)
    • 包3:服务器发送 FIN+ACK(seq=201)
    • 包4:客户端回复 ACK

    九、常见面试题

    Q1:四次挥手中哪一次可能丢失?如何应对?

    丢失的包后果应对机制
    第1次 FIN 服务器不知道要关闭 客户端重传 FIN
    第2次 ACK 客户端一直等 ACK 客户端重传 FIN
    第3次 FIN 客户端一直等 FIN 服务器重传 FIN
    第4次 ACK 服务器一直等 ACK 服务器重传 FIN(客户端在 TIME_WAIT 中响应)

    Q2:为什么 TIME_WAIT 在主动关闭方,而不是被动关闭方?

    因为主动关闭方发送了最后的 ACK,它需要确认这个 ACK 是否到达。被动关闭方发送的是 FIN,不需要等待 ACK 是否丢失(重传机制由主动方处理)。

    Q3:服务器产生大量 TIME_WAIT 说明什么?

    说明 服务器作为主动关闭方。常见场景:

    • 服务器主动关闭空闲连接
    • 短连接场景(如 HTTP/1.0 默认关闭)

    解决方案:使用长连接(HTTP Keep-Alive)或调整内核参数。

    Q4:服务器产生大量 CLOSE_WAIT 说明什么?

    说明 服务器作为被动关闭方,但应用层没有及时调用 close()。

    这是代码 BUG——socket 泄漏。排查方法:

    # 查看进程的 socket 状态
    lsof -p <pid> | grep CLOSE_WAIT

    Q5:为什么握手是三次,挥手是四次?

    握手时,服务器收到 SYN 可以立即回复 SYN+ACK(没有数据延迟问题)。挥手时,服务器收到 FIN 后需要先回复 ACK,等应用准备好后才能发送 FIN,这两个动作不能合并。


    十、总结速查表

    步骤方向报文主动方状态 →被动方状态 →
    1 主动 → 被动 FIN EST → FIN_WAIT_1 EST → CLOSE_WAIT
    2 被动 → 主动 ACK FIN_WAIT_1 → FIN_WAIT_2 CLOSE_WAIT(不变)
    3 被动 → 主动 FIN FIN_WAIT_2 → TIME_WAIT CLOSE_WAIT → LAST_ACK
    4 主动 → 被动 ACK TIME_WAIT → CLOSED(2MSL后) LAST_ACK → CLOSED

    一句话记忆:

    主动发 FIN,对方回 ACK;对方发 FIN,我回 ACK;等待 2MSL,连接正式关闭。

    核心要点:

    要点说明
    为什么四次 全双工,两个独立关闭
    半关闭 一方关闭发送,仍可接收
    TIME_WAIT 确保最后 ACK 到达 + 旧包消失
    2MSL 通常 1~4 分钟
    CLOSE_WAIT 过多 代码忘了 close socket

    在这里插入图片描述

    🌺The End🌺点点关注,收藏不迷路🌺

    ⬆ ⬆ 顶部 ⬆ ⬆

    赞(0)
    未经允许不得转载:171主机测评 » 优雅告别:TCP四次挥手完整解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址