博主智算菩萨,专注于人工智能、Python编程、音视频处理及UI窗体程序设计等方向。致力于以通俗易懂的方式拆解前沿技术,从零基础入门到高阶实战,陪伴开发者共同成长。目前已开设五大技术专栏,累计发布多篇原创技术文章,深受读者好评。

📌 专栏导航

  • 人工智能前沿知识(已更191篇):深度剖析Transformer架构、生成式AI、强化学习、具身智能、神经符号系统、大模型及智能体(Agent)技术,系统性解析AI核心技术体系与前沿趋势。
  • Python基础小白编程(已更232篇):从零开始,以保姆式教程讲解变量、数据类型、流程控制、函数等核心语法,配有大量实战代码与避坑指南,真正做到学以致用。
  • 机器学习与深度学习(125篇):系统化拆解线性模型、决策树、随机森林、梯度提升树、神经网络等算法原理与工程实践,覆盖从公式推导到代码实现的全链路内容。
  • 音频、图像与视频处理理论与实战(81篇):涵盖FFmpeg多媒体处理、audio_shop开源工具、ComfyUI-WanVideoWrapper视频生成等实用技术,从基础操作到高级应用一应俱全。
  • UI窗体程序设计实战(78篇):深入讲解UI设计、动态窗体生成、游戏UI框架设计等实战技巧,提供从配置到编码的完整解决方案。
    智算菩萨,以代码为经,以算法为纬,在人工智能的星辰大海中,做你前行路上最可靠的导航者。本人最常用AI工具为AIGCBAR

考研重点等级:⭐⭐⭐⭐⭐(核心考点)

TCP四次挥手与三次握手同等重要,是考研必考知识点。特别是TIME_WAIT状态及其2MSL超时时间的含义,几乎是每年必考的内容。理解四次挥手的关键在于理解"为什么需要四次"以及"TIME_WAIT状态的作用"。


1. TCP连接释放的背景

1.1 从"挂电话"理解连接释放

想象你和朋友通电话,通话结束时:

你:“好了,我要说的说完了。”(表明自己没有数据要发了)

朋友:“好的,我知道了。”(确认收到你的结束信号,但可能还有话要说)

朋友:“对了,还有一件事……好了,我也说完了。”(朋友的数据发完了,也想结束)

你:“好的,那我挂了。”(确认收到朋友的结束信号)

【等待一会儿,确认对方没有再说什么,才真正挂断】

这个"四次确认"的过程,就是TCP四次挥手的真实写照。与建立连接时双方"同时开始"不同,关闭连接时往往是一方先结束,另一方可能还有数据要发送,因此需要分别关闭两个方向的数据传输。

1.2 为什么关闭比建立更复杂?

对比项 连接建立(三次握手) 连接释放(四次挥手)
方向性 双方同时建立双向连接 可能一方先关闭,另一方还有数据
报文数 3个报文段 4个报文段
同时性 服务器将SYN和ACK合并 FIN和ACK通常不能合并
最终状态 ESTABLISHED TIME_WAIT + CLOSED
资源分配 分配资源 需要等待后才释放资源

💡 核心差异:三次握手中,服务器的SYN和ACK可以合并成一个报文段;但四次挥手中,被动方收到FIN后可能还有数据要发,所以不能立即发FIN,必须先发ACK确认,等数据发完再发FIN。这就导致了ACK和FIN不能合并,需要四次报文交换。


2. TCP四次挥手的详细过程

2.1 四次挥手全景概览

假设客户端先主动关闭连接(实际上,服务器也可以主动关闭,两种情况都需要掌握)。

【图1】TCP四次挥手完整时序图

服务器 (ESTABLISHED) 客户端 (ESTABLISHED) 服务器 (ESTABLISHED) 客户端 (ESTABLISHED) 第一次挥手(主动方发送FIN) 服务器收到FIN 知道客户端没有数据要发了 第二次挥手(被动方回复ACK) 客户端→服务器方向关闭 半关闭状态 服务器仍可发送数据! 第三次挥手(被动方发送FIN) 服务器也没有数据要发了 第四次挥手(主动方回复ACK) 客户端进入TIME_WAIT 等待2MSL后关闭 服务器收到ACK后关闭 FIN=1, seq=u (FIN报文段,不携带数据) 1 ACK=1, seq=v, ack=u+1 (确认收到FIN) 2 FIN=1, ACK=1, seq=w, ack=u+1 (FIN+ACK报文段) 3 ACK=1, seq=u+1, ack=w+1 (确认收到FIN) 4

2.2 状态转换图

【图2】TCP四次挥手完整状态转换图

服务器被动关闭

收到FIN
发送ACK

发送FIN

收到ACK

ESTABLISHED

CLOSE-WAIT

LAST-ACK

CLOSED

客户端主动关闭

发送FIN

收到ACK

收到FIN
发送ACK

同时收到FIN+ACK
发送ACK

2MSL超时

ESTABLISHED

FIN-WAIT-1

FIN-WAIT-2

TIME-WAIT
等待2MSL

CLOSED

🔥 注意:上图展示了客户端主动关闭的情况。如果服务器主动关闭,双方的状态名称会互换(服务器走FIN-WAIT路径,客户端走CLOSE-WAIT路径)。

2.3 第一次挥手:主动方发送FIN

报文内容FIN=1, seq=u

详细解读

当主动方(假设为客户端)的数据发送完毕后,调用close()shutdown(),发送第一个FIN报文段。

  • FIN=1:结束标志位(Finish),表示"我没有数据要发送了,请求关闭连接"。
  • seq=u:序号。u是客户端当前发送序号的一个值。FIN报文段不携带数据,但它消耗一个序号(与SYN类似)。

关键点

  • 发送FIN后,客户端进入FIN-WAIT-1状态。
  • 此时客户端仍然可以接收服务器发来的数据(半关闭状态)。
  • FIN报文段不携带数据,但消耗序号u

2.4 第二次挥手:被动方回复ACK

报文内容ACK=1, seq=v, ack=u+1

详细解读

服务器收到客户端的FIN后,立即回复ACK确认。

  • ACK=1:确认标志位置1。
  • seq=v:服务器的序号。v是服务器当前发送序号,等于服务器之前发送数据的最后一个字节序号+1。
  • ack=u+1:确认号。由于FIN消耗了一个序号u,所以ack=u+1,表示"我已收到序号u及之前的所有数据"。

关键点

  • 服务器发送ACK后,进入CLOSE-WAIT状态。
  • CLOSE-WAIT状态的含义:服务器已经知道客户端没有数据要发了,但服务器自己可能还有数据要发送
  • 在CLOSE-WAIT状态下,服务器仍然可以继续发送数据给客户端(这就是半关闭的含义)。
  • 客户端收到这个ACK后,从FIN-WAIT-1进入FIN-WAIT-2状态。

💡 类比理解:就像你说"我说完了",对方说"好的我知道了",但对方可能还有话要说,所以通话还不能完全结束。

2.5 第三次挥手:被动方发送FIN

报文内容FIN=1, ACK=1, seq=w, ack=u+1

详细解读

当服务器的数据也发送完毕后,发送自己的FIN报文段。

  • FIN=1:服务器也没有数据要发送了。
  • ACK=1:ACK标志仍然置1。
  • seq=w:序号。注意w可能不等于v!因为在CLOSE-WAIT状态期间,服务器可能还发送了一些数据,序号从v增长到了w
  • ack=u+1:确认号仍然是u+1,因为从第二次挥手到现在,客户端没有发送新的数据。

关键点

  • 服务器发送FIN后,进入LAST-ACK状态(最后确认状态)。
  • 这是服务器最后一次发送报文,等待客户端的最终ACK。

2.6 第四次挥手:主动方回复ACK

报文内容ACK=1, seq=u+1, ack=w+1

详细解读

客户端收到服务器的FIN后,发送最后的ACK确认。

  • ACK=1:确认标志位置1。
  • seq=u+1:客户端的序号。由于第一次挥手的FIN消耗了序号u,所以当前序号是u+1
  • ack=w+1:确认号,表示"我已收到序号w及之前的所有数据(包括你的FIN)"。

关键点

  • 发送ACK后,客户端不立即关闭连接,而是进入TIME-WAIT状态。
  • 客户端在TIME-WAIT状态等待2MSL时间后,才进入CLOSED状态。
  • 服务器收到这个ACK后,立即进入CLOSED状态。
  • 如果服务器没有收到这个ACK(ACK丢失),服务器会重发FIN,客户端在TIME-WAIT状态能收到这个重发的FIN并重新发送ACK。

2.7 四次挥手报文汇总

挥手次数 发送方 报文类型 FIN ACK seq值 ack值 是否消耗序号 状态变化
第一次 主动方 FIN 1 0 u 消耗 ESTAB→FIN-WAIT-1
第二次 被动方 ACK 0 1 v u+1 不消耗 ESTAB→CLOSE-WAIT
第三次 被动方 FIN+ACK 1 1 w u+1 消耗 CLOSE-WAIT→LAST-ACK
第四次 主动方 ACK 0 1 u+1 w+1 不消耗 FIN-WAIT-2→TIME-WAIT

3. TIME_WAIT状态详解(考研必考)

3.1 什么是TIME_WAIT?

TIME_WAIT是TCP连接释放过程中一个特殊的状态。主动关闭方在发送最后一个ACK后,不会立即关闭连接,而是进入TIME_WAIT状态,等待2MSL(2倍的最长报文段寿命)时间后才真正关闭。

进入

2MSL时间到

收到重发的FIN
重发ACK,重置计时器

发送最后一个ACK

TIME-WAIT
等待2MSL

CLOSED

3.2 TIME_WAIT的两个重要作用

作用1:保证主动方发送的最后一个ACK能被被动方收到

这是TIME_WAIT最直接的作用。考虑以下场景:

服务器 (LAST-ACK) 客户端 (TIME-WAIT) 服务器 (LAST-ACK) 客户端 (TIME-WAIT) 场景:最后一个ACK丢失 ❌ ACK丢失! 服务器等待ACK超时 重发FIN报文段 客户端在TIME-WAIT状态 仍能接收FIN! 服务器收到ACK 正常关闭 重置2MSL计时器 继续等待... ACK, seq=u+1, ack=w+1 1 FIN=1, seq=w(重发) 2 ACK, seq=u+1, ack=w+1(重发) 3 2MSL时间到 → CLOSED 4

如果客户端发送最后一个ACK后立即关闭:

  • 如果这个ACK丢失了,服务器会重发FIN
  • 但客户端已经关闭了,不会响应这个FIN
  • 服务器永远收不到确认,无法正确关闭

而在TIME_WAIT状态下:

  • 如果ACK丢失,服务器重发的FIN会到达客户端
  • 客户端重新发送ACK
  • 重置2MSL计时器,继续等待
  • 确保服务器能正确关闭

作用2:防止已失效的连接请求报文出现在后续连接中

这个作用与三次握手中防止失效连接的原理类似:

  • 假设客户端发送最后一个ACK后立即关闭
  • 网络中可能还有之前连接的延迟报文段
  • 如果客户端立即用相同的端口建立新连接
  • 这些"旧"报文段可能被新连接误认为是有效数据

TIME_WAIT等待2MSL确保了:

  • 网络中所有来自旧连接的报文段都已经消失(因为它们的寿命最长为MSL)
  • 新连接不会收到旧的、延迟的报文段

3.3 MSL的定义与取值

MSL(Maximum Segment Lifetime)是最长报文段寿命,即一个TCP报文段在网络中存活的最长时间。

参数 含义 典型值
MSL 最长报文段寿命 2分钟(RFC推荐)
2MSL TIME_WAIT等待时间 4分钟
Linux默认值 /proc/sys/net/ipv4/tcp_fin_timeout 通常60秒

🔥 考研考点:TIME_WAIT等待时间是2MSL,不是MSL。为什么是2MSL?因为最坏情况下,最后一个ACK需要一个MSL到达对方,如果对方重发FIN,又需要一个MSL到达自己。所以总共需要2MSL来确保双方都能正确关闭。

3.4 TIME_WAIT的潜在问题

在高并发的服务器场景中,TIME_WAIT状态可能导致问题:

  • 每个连接在主动关闭后都会占用一个TIME_WAIT状态的端口
  • 如果TIME_WAIT连接过多,可能耗尽可用端口
  • 解决方案:连接复用(Connection Reuse)、缩短TIME_WAIT时间、使用被动关闭策略

4. 为什么需要四次挥手?三次行不行?

这是考研中另一个高频问题。核心答案是:因为TCP是全双工通信,两个方向的关闭需要独立进行

4.1 不能合并成三次的原因

关键问题:被动方收到FIN后,可能还有数据要发送。

服务器 客户端 服务器 客户端 为什么不能三次挥手? 服务器收到FIN 但还有数据没发完! 【必须先发ACK确认收到FIN】 【然后继续发送剩余数据】 【服务器继续发送数据...】 数据发完了 FIN(我没有数据了) 1 ACK(我知道了,你先等着) 2 [数据1] 3 [数据2] 4 FIN(我也没有数据了) 5 ACK(好的,关闭吧) 6

第二次挥手的ACK和第三次挥手的FIN通常不能合并,因为:

  1. 服务器收到FIN后,必须立即回复ACK(确认收到)
  2. 但服务器可能还有数据没发完
  3. 等数据发完后,才能发送FIN
  4. 这两个操作有时间差,所以不能合并

4.2 特殊情况:三次挥手也是可能的

在某些特殊情况下,四次挥手可以变成三次:

  • 如果被动方在收到FIN时,恰好也没有数据要发送了
  • 那么ACK和FIN可以合并成一个报文段(捎带确认)
  • 此时只需要三次报文交换

但这只是特殊情况,不是一般情况,所以TCP协议设计为四次挥手。

4.3 对比:三次握手vs四次挥手

对比项 三次握手 四次挥手
报文数 3 4
SYN+ACK合并 可以(服务器同时发SYN和ACK) FIN+ACK通常不能合并
不能合并的原因 N/A 被动方可能还有数据
最终等待 不需要 TIME_WAIT (2MSL)
双方状态 同时ESTABLISHED 关闭有先后顺序

5. 优雅关闭 vs 强制关闭(RST)

5.1 优雅关闭(Graceful Close)

四次挥手就是优雅关闭的过程:

  • 双方通过FIN/ACK协商关闭连接
  • 所有已发送的数据都能被对方接收
  • 连接有序地释放,不丢失数据

5.2 强制关闭(RST复位)

在某些情况下,TCP使用RST(Reset)标志位来强制关闭连接:

触发RST的常见场景

场景 说明
端口未监听 向一个没有进程监听的端口发送SYN
连接异常 收到一个不属于任何已知连接的报文段
超时过重置 某些实现中连接超时时发送RST
主动异常关闭 程序崩溃或强制终止时

RST报文段的特点

  • RST=1,不消耗序号(特殊情况)
  • 收到RST的一方立即关闭连接
  • 不经过四次挥手的协商过程
  • 可能导致数据丢失
服务器 客户端 服务器 客户端 优雅关闭 vs 强制关闭 优雅关闭(四次挥手) 所有数据都已送达 资源有序释放 强制关闭(RST) 立即关闭! 不管有没有未处理数据 FIN 1 ACK 2 FIN 3 ACK 4 RST=1 5

6. 实战:Python模拟TCP四次挥手过程

下面用Python代码完整模拟TCP四次挥手的过程,包括TIME_WAIT状态的模拟。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TCP四次挥手模拟器
完整模拟TCP连接释放过程,包括:
1. 四次报文交换
2. 状态转换
3. TIME_WAIT状态和2MSL等待
4. 详细的调试输出

使用方法:
    python tcp_disconnect.py
"""

import time
import threading


class TCPPacket:
    """TCP报文段模拟类(复用P44的定义)"""

    def __init__(self, src_port, dst_port, seq, ack=0, syn=0, 
                 ack_flag=0, fin=0, rst=0, data=""):
        self.src_port = src_port
        self.dst_port = dst_port
        self.seq = seq
        self.ack = ack
        self.syn = syn
        self.ack_flag = ack_flag
        self.fin = fin
        self.rst = rst
        self.data = data

    def __str__(self):
        flags = []
        if self.syn:
            flags.append("SYN=1")
        if self.ack_flag:
            flags.append("ACK=1")
        if self.fin:
            flags.append("FIN=1")
        if self.rst:
            flags.append("RST=1")
        flag_str = ", ".join(flags) if flags else "无标志位"
        result = f"[{flag_str}]"
        result += f", seq={self.seq}"
        if self.ack_flag or self.syn:
            result += f", ack={self.ack}"
        if self.data:
            result += f", 数据='{self.data}'({len(self.data)}字节)"
        return result


class TCPDisconnectSimulator:
    """
    TCP四次挥手模拟器

    模拟客户端和服务器之间释放TCP连接的完整四次挥手过程。
    包含完整的状态转换和TIME_WAIT状态模拟。

    状态机(客户端主动关闭):
        客户端: ESTAB -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT -> CLOSED
        服务器: ESTAB -> CLOSE-WAIT -> LAST-ACK -> CLOSED
    """

    # 类常量:MSL时间(模拟用,实际为2分钟)
    MSL = 2  # 模拟2秒为1个MSL,2MSL = 4秒

    def __init__(self, client_port=54321, server_port=80):
        """
        初始化模拟器

        参数:
            client_port: 客户端端口号
            server_port: 服务器端口号
        """
        # 客户端状态
        self.client_port = client_port
        self.client_seq = 1500  # 客户端当前序号
        self.client_state = "ESTABLISHED"

        # 服务器状态
        self.server_port = server_port
        self.server_seq = 2500  # 服务器当前序号
        self.server_state = "ESTABLISHED"

        # 数据传输标记
        self.server_has_more_data = True  # 服务器是否还有数据要发

        print("=" * 70)
        print("           TCP 四次挥手连接释放模拟器")
        print("=" * 70)
        print(f"\n📋 初始化参数:")
        print(f"   客户端端口: {self.client_port}, 当前序号: {self.client_seq}")
        print(f"   服务器端口: {self.server_port}, 当前序号: {self.server_seq}")
        print(f"   客户端状态: {self.client_state}")
        print(f"   服务器状态: {self.server_state}")
        print(f"   模拟MSL时间: {self.MSL}秒")
        print("=" * 70)

    def log_step(self, step_num, title, detail=""):
        """打印步骤分隔符"""
        print(f"\n{'─' * 70}")
        print(f"【第{step_num}步】{title}")
        if detail:
            print(f"   {detail}")
        print(f"{'─' * 70}")

    def log_state(self):
        """打印当前状态"""
        print(f"   [状态] 客户端: {self.client_state:<20} "
              f"| 服务器: {self.server_state}")

    # ==================== 第一次挥手 ====================
    def client_send_fin(self):
        """
        主动方(客户端)发送FIN报文段(第一次挥手)

        报文内容: FIN=1, seq=u
        不携带数据,但消耗一个序号
        """
        self.log_step(1, "第一次挥手:客户端 → 服务器 (FIN)",
                      "客户端数据发送完毕,请求关闭连接")

        u = self.client_seq
        packet = TCPPacket(
            src_port=self.client_port,
            dst_port=self.server_port,
            seq=u,
            fin=1,
            ack=0,
            ack_flag=0
        )

        print(f"   📤 客户端发送: {packet}")
        print(f"   💡 说明: FIN=1表示关闭请求,seq={u}")
        print(f"   💡 说明: FIN报文段不携带数据,但消耗一个序号")
        print(f"   💡 说明: 客户端→服务器方向的写通道关闭")

        # 状态转换
        self.client_state = "FIN-WAIT-1"
        self.client_seq = u + 1  # FIN消耗一个序号
        print(f"   🔄 客户端状态转换: ESTABLISHED → FIN-WAIT-1")
        self.log_state()

        return packet

    def server_receive_fin(self, fin_packet):
        """
        被动方(服务器)收到FIN,回复ACK(第二次挥手)

        报文内容: ACK=1, seq=v, ack=u+1

        参数:
            fin_packet: 收到的FIN报文段
        """
        self.log_step(2, "第二次挥手:服务器 → 客户端 (ACK)",
                      "服务器收到FIN,回复ACK确认")

        u = fin_packet.seq
        v = self.server_seq
        packet = TCPPacket(
            src_port=self.server_port,
            dst_port=self.client_port,
            seq=v,
            ack=u + 1,
            ack_flag=1
        )

        print(f"   📥 服务器收到: {fin_packet}")
        print(f"   📤 服务器发送: {packet}")
        print(f"   💡 说明: ACK=1, ack={u}+1={u+1} 确认收到客户端的FIN")
        print(f"   💡 说明: 服务器 seq={v},还有数据要发送...")
        print(f"   💡 说明: ⚠️ 注意:服务器仍可向客户端发送数据!")

        # 状态转换
        self.server_state = "CLOSE-WAIT"
        print(f"   🔄 服务器状态转换: ESTABLISHED → CLOSE-WAIT")
        self.log_state()

        return packet

    def server_send_data_during_close_wait(self):
        """
        模拟服务器在CLOSE-WAIT状态下继续发送数据
        这是四次挥手的关键——半关闭状态
        """
        if not self.server_has_more_data:
            return []

        print(f"\n   📤 [服务器在CLOSE-WAIT状态继续发送数据]")
        packets = []
        for i, data in enumerate(["剩余数据1", "剩余数据2"]):
            packet = TCPPacket(
                src_port=self.server_port,
                dst_port=self.client_port,
                seq=self.server_seq,
                ack_flag=1,
                ack=self.client_seq,  # 确认号
                data=data
            )
            print(f"      发送数据: seq={self.server_seq}, "
                  f"data='{data}'")
            self.server_seq += len(data)
            packets.append(packet)

        print(f"   💡 说明: 半关闭状态下,服务器→客户端方向仍然开放")
        print(f"   💡 说明: 服务器序号从 {self.server_seq - sum(len(d) for d in ['剩余数据1', '剩余数据2'])} "
              f"增长到 {self.server_seq}")

        return packets

    # ==================== 第三次挥手 ====================
    def server_send_fin(self):
        """
        被动方(服务器)数据发送完毕,发送FIN(第三次挥手)

        报文内容: FIN=1, ACK=1, seq=w, ack=u+1
        """
        self.log_step(3, "第三次挥手:服务器 → 客户端 (FIN+ACK)",
                      "服务器数据也发送完毕,发送FIN请求关闭")

        w = self.server_seq
        packet = TCPPacket(
            src_port=self.server_port,
            dst_port=self.client_port,
            seq=w,
            ack=self.client_seq,
            fin=1,
            ack_flag=1
        )

        print(f"   📤 服务器发送: {packet}")
        print(f"   💡 说明: FIN=1, ACK=1,seq={w}")
        print(f"   💡 说明: 注意seq={w} > 之前的seq值,因为中间发了数据")
        print(f"   💡 说明: ack={self.client_seq},客户端没有新数据")

        # 状态转换
        self.server_state = "LAST-ACK"
        self.server_seq = w + 1  # FIN消耗一个序号
        print(f"   🔄 服务器状态转换: CLOSE-WAIT → LAST-ACK")
        self.log_state()

        return packet

    def client_receive_ack(self, ack_packet):
        """
        客户端收到第二次挥手的ACK
        从FIN-WAIT-1进入FIN-WAIT-2

        参数:
            ack_packet: 收到的ACK报文段
        """
        print(f"\n   📥 客户端收到: {ack_packet}")
        print(f"   💡 说明: 服务器确认收到我的FIN了")

        self.client_state = "FIN-WAIT-2"
        print(f"   🔄 客户端状态转换: FIN-WAIT-1 → FIN-WAIT-2")
        self.log_state()

    # ==================== 第四次挥手 ====================
    def client_receive_fin(self, fin_packet):
        """
        主动方(客户端)收到FIN,回复ACK,进入TIME_WAIT(第四次挥手)

        报文内容: ACK=1, seq=u+1, ack=w+1

        参数:
            fin_packet: 收到的FIN报文段
        """
        self.log_step(4, "第四次挥手:客户端 → 服务器 (ACK)",
                      "客户端收到FIN,回复ACK,进入TIME_WAIT")

        w = fin_packet.seq
        packet = TCPPacket(
            src_port=self.client_port,
            dst_port=self.server_port,
            seq=self.client_seq,
            ack=w + 1,
            ack_flag=1
        )

        print(f"   📥 客户端收到: {fin_packet}")
        print(f"   📤 客户端发送: {packet}")
        print(f"   💡 说明: ACK=1, ack={w}+1={w+1} 确认收到服务器的FIN")
        print(f"   💡 说明: seq={self.client_seq}(FIN-WAIT-1之后没有新数据)")

        # 状态转换 - 进入TIME_WAIT
        self.client_state = "TIME-WAIT"
        print(f"   🔄 客户端状态转换: FIN-WAIT-2 → TIME-WAIT")
        self.log_state()

        return packet

    def server_receive_ack(self, ack_packet):
        """
        服务器收到最后一个ACK,连接关闭

        参数:
            ack_packet: 收到的ACK报文段
        """
        print(f"\n   📥 服务器收到: {ack_packet}")
        print(f"   ✅ 服务器收到最终ACK,连接关闭!")

        self.server_state = "CLOSED"
        print(f"   🔄 服务器状态转换: LAST-ACK → CLOSED")
        self.log_state()

    # ==================== TIME_WAIT等待 ====================
    def time_wait_expire(self):
        """
        TIME_WAIT超时(2MSL),连接完全关闭
        这是连接释放的最后一步
        """
        print(f"\n{'─' * 70}")
        print(f"【TIME_WAIT阶段】等待2MSL = {2 * self.MSL}秒")
        print(f"{'─' * 70}")

        print(f"   ⏱️  客户端进入TIME_WAIT状态")
        print(f"   💡 作用1: 保证最后一个ACK能被服务器收到")
        print(f"   💡    如果ACK丢失,服务器会重发FIN")
        print(f"   💡    客户端在TIME_WAIT状态仍能响应")
        print(f"   💡 作用2: 防止旧连接的报文干扰新连接")
        print(f"   💡    等待2MSL确保网络中所有旧报文消失")

        # 模拟等待
        print(f"   ⏳ 开始等待 {2 * self.MSL}秒...")
        # time.sleep(2 * self.MSL)  # 实际运行时取消注释

        # 模拟可能的重发FIN场景
        print(f"\n   📋 场景模拟: 假设最后一个ACK丢失了...")
        print(f"   📥 服务器在LAST-ACK状态超时,重发FIN")
        print(f"   📤 客户端在TIME_WAIT状态收到FIN,重发ACK")
        print(f"   ✅ TIME_WAIT确保了这种容错能力!")

        # 最终关闭
        self.client_state = "CLOSED"
        print(f"\n   ⏰ {2 * self.MSL}秒时间到!")
        print(f"   ✅ 客户端连接完全关闭!")
        print(f"   🔄 客户端状态转换: TIME-WAIT → CLOSED")
        self.log_state()

    # ==================== 完整模拟 ====================
    def simulate(self):
        """
        模拟完整的四次挥手过程

        按顺序执行:
        1. 客户端发送FIN(第一次挥手)
        2. 服务器回复ACK(第二次挥手)
        3. 服务器继续发送数据(半关闭状态演示)
        4. 服务器发送FIN(第三次挥手)
        5. 客户端回复ACK,进入TIME_WAIT(第四次挥手)
        6. TIME_WAIT超时,连接关闭
        """
        print("\n🔔 开始模拟TCP四次挥手...\n")

        # 第一次挥手
        fin = self.client_send_fin()
        time.sleep(0.5)

        # 第二次挥手
        ack = self.server_receive_fin(fin)
        time.sleep(0.5)

        # 客户端收到ACK,进入FIN-WAIT-2
        self.client_receive_ack(ack)
        time.sleep(0.5)

        # 服务器在CLOSE-WAIT状态下继续发送数据(半关闭演示)
        self.server_send_data_during_close_wait()
        time.sleep(0.5)

        # 第三次挥手
        fin2 = self.server_send_fin()
        time.sleep(0.5)

        # 第四次挥手
        ack2 = self.client_receive_fin(fin2)
        time.sleep(0.5)

        # 服务器收到ACK,关闭
        self.server_receive_ack(ack2)

        # TIME_WAIT阶段
        self.time_wait_expire()

        # 打印总结
        self.print_summary()

    def print_summary(self):
        """打印挥手过程总结"""
        print("\n" + "=" * 70)
        print("                       挥手过程总结")
        print("=" * 70)

        print("\n📊 序号变化总结:")
        print(f"   客户端: 初始seq = 1500, FIN后seq = 1501")
        print(f"   服务器: 初始seq = 2500")
        print(f"           发送数据后增长到 {self.server_seq - 1}")

        print("\n🔄 客户端状态转换:")
        print("   ESTABLISHED → FIN-WAIT-1 → FIN-WAIT-2 → TIME-WAIT → CLOSED")
        print("\n🔄 服务器状态转换:")
        print("   ESTABLISHED → CLOSE-WAIT → LAST-ACK → CLOSED")

        print("\n📋 四次挥手核心要点:")
        print("   1. 第一次: FIN=1, seq=u(消耗一个序号)")
        print("   2. 第二次: ACK=1, seq=v, ack=u+1")
        print("   3. 第三次: FIN=1, ACK=1, seq=w, ack=u+1")
        print("   4. 第四次: ACK=1, seq=u+1, ack=w+1")
        print("\n📋 TIME_WAIT要点:")
        print(f"   - 等待时间: 2MSL")
        print("   - 作用1: 确保最后一个ACK能被收到")
        print("   - 作用2: 防止旧连接报文干扰新连接")
        print("=" * 70)


def main():
    """主程序:运行TCP四次挥手模拟器"""
    simulator = TCPDisconnectSimulator(
        client_port=54321,
        server_port=80
    )
    simulator.simulate()

    print("\n✅ 模拟完成!")


if __name__ == "__main__":
    main()

运行结果示例

======================================================================
           TCP 四次挥手连接释放模拟器
======================================================================

📋 初始化参数:
   客户端端口: 54321, 当前序号: 1500
   服务器端口: 80, 当前序号: 2500
   客户端状态: ESTABLISHED
   服务器状态: ESTABLISHED
   模拟MSL时间: 2秒
======================================================================

🔔 开始模拟TCP四次挥手...

----------------------------------------------------------------------
【第1步】第一次挥手:客户端 → 服务器 (FIN)
   客户端数据发送完毕,请求关闭连接
----------------------------------------------------------------------
   📤 客户端发送: [FIN=1], seq=1500
   💡 说明: FIN=1表示关闭请求,seq=1500
   💡 说明: FIN报文段不携带数据,但消耗一个序号
   💡 说明: 客户端→服务器方向的写通道关闭
   🔄 客户端状态转换: ESTABLISHED → FIN-WAIT-1
   [状态] 客户端: FIN-WAIT-1             | 服务器: ESTABLISHED

----------------------------------------------------------------------
【第2步】第二次挥手:服务器 → 客户端 (ACK)
   服务器收到FIN,回复ACK确认
----------------------------------------------------------------------
   📥 服务器收到: [FIN=1], seq=1500
   📤 服务器发送: [ACK=1], seq=2500, ack=1501
   💡 说明: ACK=1, ack=1500+1=1501 确认收到客户端的FIN
   💡 说明: 服务器 seq=2500,还有数据要发送...
   💡 说明: ⚠️ 注意:服务器仍可向客户端发送数据!
   🔄 服务器状态转换: ESTABLISHED → CLOSE-WAIT
   [状态] 客户端: FIN-WAIT-1             | 服务器: CLOSE-WAIT

   📥 客户端收到: [ACK=1], seq=2500, ack=1501
   💡 说明: 服务器确认收到我的FIN了
   🔄 客户端状态转换: FIN-WAIT-1 → FIN-WAIT-2
   [状态] 客户端: FIN-WAIT-2             | 服务器: CLOSE-WAIT

   📤 [服务器在CLOSE-WAIT状态继续发送数据]
      发送数据: seq=2500, data='剩余数据1'
      发送数据: seq=2510, data='剩余数据2'
   💡 说明: 半关闭状态下,服务器→客户端方向仍然开放
   💡 说明: 服务器序号从 2500 增长到 2520

----------------------------------------------------------------------
【第3步】第三次挥手:服务器 → 客户端 (FIN+ACK)
   服务器数据也发送完毕,发送FIN请求关闭
----------------------------------------------------------------------
   📤 服务器发送: [FIN=1, ACK=1], seq=2520, ack=1501
   💡 说明: FIN=1, ACK=1,seq=2520
   💡 说明: 注意seq=2520 > 之前的seq值,因为中间发了数据
   💡 说明: ack=1501,客户端没有新数据
   🔄 服务器状态转换: CLOSE-WAIT → LAST-ACK
   [状态] 客户端: FIN-WAIT-2             | 服务器: LAST-ACK

----------------------------------------------------------------------
【第4步】第四次挥手:客户端 → 服务器 (ACK)
   客户端收到FIN,回复ACK,进入TIME_WAIT
----------------------------------------------------------------------
   📥 客户端收到: [FIN=1, ACK=1], seq=2520, ack=1501
   📤 客户端发送: [ACK=1], seq=1501, ack=2521
   💡 说明: ACK=1, ack=2520+1=2521 确认收到服务器的FIN
   💡 说明: seq=1501(FIN-WAIT-1之后没有新数据)
   🔄 客户端状态转换: FIN-WAIT-2 → TIME-WAIT
   [状态] 客户端: TIME-WAIT              | 服务器: LAST-ACK

   📥 服务器收到: [ACK=1], seq=1501, ack=2521
   ✅ 服务器收到最终ACK,连接关闭!
   🔄 服务器状态转换: LAST-ACK → CLOSED
   [状态] 客户端: TIME-WAIT              | 服务器: CLOSED

----------------------------------------------------------------------
【TIME_WAIT阶段】等待2MSL = 4秒
----------------------------------------------------------------------
   ⏱️  客户端进入TIME_WAIT状态
   💡 作用1: 保证最后一个ACK能被服务器收到
   💡    如果ACK丢失,服务器会重发FIN
   💡    客户端在TIME_WAIT状态仍能响应
   💡 作用2: 防止旧连接的报文干扰新连接
   💡    等待2MSL确保网络中所有旧报文消失
   ⏳ 开始等待 4秒...

   📋 场景模拟: 假设最后一个ACK丢失了...
   📥 服务器在LAST-ACK状态超时,重发FIN
   📤 客户端在TIME_WAIT状态收到FIN,重发ACK
   ✅ TIME_WAIT确保了这种容错能力!

   ⏰ 4秒时间到!
   ✅ 客户端连接完全关闭!
   🔄 客户端状态转换: TIME-WAIT → CLOSED
   [状态] 客户端: CLOSED                 | 服务器: CLOSED

======================================================================
                       挥手过程总结
======================================================================

📊 序号变化总结:
   客户端: 初始seq = 1500, FIN后seq = 1501
   服务器: 初始seq = 2500
           发送数据后增长到 2520

🔄 客户端状态转换:
   ESTABLISHED → FIN-WAIT-1 → FIN-WAIT-2 → TIME-WAIT → CLOSED

🔄 服务器状态转换:
   ESTABLISHED → CLOSE-WAIT → LAST-ACK → CLOSED

📋 四次挥手核心要点:
   1. 第一次: FIN=1, seq=u(消耗一个序号)
   2. 第二次: ACK=1, seq=v, ack=u+1
   3. 第三次: FIN=1, ACK=1, seq=w, ack=u+1
   4. 第四次: ACK=1, seq=u+1, ack=w+1

📋 TIME_WAIT要点:
   - 等待时间: 2MSL
   - 作用1: 确保最后一个ACK能被收到
   - 作用2: 防止旧连接报文干扰新连接
======================================================================

✅ 模拟完成!

7. 经典例题解析

例题1(TIME_WAIT状态作用)

题目:TCP连接释放时,主动关闭方为什么要进入TIME_WAIT状态并等待2MSL时间?

标准答案

TIME_WAIT状态有两个重要作用:

第一,保证主动关闭方发送的最后一个ACK能被被动关闭方收到。 如果最后一个ACK丢失,被动方会超时重发FIN报文段。主动方在TIME_WAIT状态下仍能收到这个重发的FIN并重新发送ACK。如果主动方发送ACK后立即关闭,就无法响应重发的FIN,导致被动方无法正常关闭。

第二,防止已失效的连接请求报文段出现在后续连接中。 等待2MSL可以确保网络中所有来自旧连接的报文段都已经消失(一个报文段的最长寿命为MSL),避免这些旧报文被后续使用相同端口的新连接误接收。

例题2(状态转换分析)

题目:假设客户端主动关闭TCP连接,请画出客户端和服务器的完整状态转换序列,并标注每个转换的触发条件。

解答

客户端

ESTABLISHED --(发送FIN)--> FIN-WAIT-1 --(收到ACK)--> FIN-WAIT-2 
                                                       |
                                                       v
                                                  CLOSED <-- TIME-WAIT <--(收到FIN,发送ACK)--

服务器

ESTABLISHED --(收到FIN,发送ACK)--> CLOSE-WAIT --(发送FIN)--> LAST-ACK --(收到ACK)--> CLOSED

例题3(序号与确认号计算)

题目:在TCP四次挥手中,若客户端当前序号seq=200,服务器当前序号seq=500。假设双方都没有再发送数据,请填写下表:

挥手次数 发送方 FIN ACK seq ack
第一次 客户端 1 0 ?
第二次 服务器 0 1 ? ?
第三次 服务器 1 1 ? ?
第四次 客户端 0 1 ? ?

解答

挥手次数 发送方 FIN ACK seq ack
第一次 客户端 1 0 200
第二次 服务器 0 1 500 201
第三次 服务器 1 1 500 201
第四次 客户端 0 1 201 501

计算说明

  • 第一次:seq = 200,FIN消耗序号200
  • 第二次:seq = 500(服务器序号不变),ack = 200 + 1 = 201
  • 第三次:seq = 500(服务器没发数据,序号不变),ack = 201
  • 第四次:seq = 200 + 1 = 201ack = 500 + 1 = 501

⚠️ 注意:本题假设"双方都没有再发送数据",所以服务器的seq始终是500。如果有数据传输,第三次的seq会大于500。

例题4(CLOSE-WAIT状态分析)

题目:某服务器发现大量连接处于CLOSE-WAIT状态,可能是什么原因?如何排查?

解答

CLOSE-WAIT状态表示服务器已经收到了对方的FIN,但还没有发送自己的FIN。大量CLOSE-WAIT状态通常说明:

  1. 服务器程序有Bug:收到对方的关闭请求后,程序没有调用close()来关闭连接
  2. 服务器还在处理数据:程序确实还在发送数据(正常情况,但应该尽快完成)
  3. 服务器端程序阻塞:程序卡在某个地方,无法执行关闭操作

排查方法:

  • 检查服务器代码是否在收到EOF时正确调用了close()
  • 使用netstat -n | grep CLOSE_WAIT查看具体连接
  • 检查服务器日志,看是否有异常或阻塞

8. 考研重点速记

8.1 必背要点

要点 内容
四次挥手报文 ①FIN ②ACK ③FIN+ACK ④ACK
FIN报文段 不携带数据,但消耗一个序号
半关闭状态 CLOSE-WAIT期间被动方仍可发送数据
TIME_WAIT等待 2MSL,不是MSL
MSL含义 Maximum Segment Lifetime,最长报文段寿命
MSL典型值 2分钟,2MSL = 4分钟
TIME_WAIT作用1 确保最后一个ACK能被收到(容错)
TIME_WAIT作用2 防止旧连接报文干扰新连接
为什么四次 被动方可能还有数据,ACK和FIN不能合并

8.2 三次握手 vs 四次挥手 终极对比

对比项 三次握手 四次挥手
报文数 3 4
第一个报文 SYN FIN
SYN+ACK合并 ✅ 可以 ❌ 通常不行
不合并原因 N/A 被动方可能还有数据
消耗序号的报文 SYN, SYN+ACK FIN, FIN+ACK
最终状态 ESTABLISHED TIME_WAIT + CLOSED
等待机制 2MSL
双方同步性 同时就绪 有先后顺序

8.3 状态转换速查表

状态 含义 属于哪方
LISTEN 监听连接请求 服务器
SYN-SENT SYN已发送 客户端
SYN-RCVD SYN已收到 服务器
ESTABLISHED 连接已建立 双方
FIN-WAIT-1 FIN已发送 主动关闭方
FIN-WAIT-2 FIN已发送且ACK已收到 主动关闭方
CLOSE-WAIT 收到FIN,等待关闭 被动关闭方
LAST-ACK FIN已发送,等待最后ACK 被动关闭方
TIME-WAIT 等待2MSL 主动关闭方
CLOSED 连接关闭 双方

考研寄语:四次挥手的核心是理解"全双工分别关闭"和"TIME_WAIT的两个作用"。熟记状态转换图,做到看到一个状态就能立刻反应出它是哪一方、处于什么阶段、下一步会转换到什么状态。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐