公司动态
2-L4-传输层-4-day05
TCP 四次挥手详解抓包视角- 第二部分目录导航一、四次挥手时序流程图二、TCP 头字段解析四次挥手部分2.1 核心字段变化表2.2 为什么 FIN 要占用序列号三、四次挥手深度解析3.1 为什么需要四次挥手3.2 报文详细分析四、状态转换完整流程4.1 客户端状态转换4.2 服务器状态转换4.3 状态详解五、常见问题Q1: 为什么 TIME_WAIT 状态需要等待 2MSLQ2: TIME_WAIT 过多会有什么问题如何解决Q3: 如果第四次挥手ACK丢失会怎样Q4: 主动关闭和被动关闭有什么区别六、Wireshark 技巧6.1 抓包过滤器6.2 显示过滤器一、四次挥手时序流程图时间轴 客户端Client 服务器Server │ │ │ │ │ ┌──────────────────────┐ │ │ │ │ 【四次挥手开始】 │ │ │ │ │ 客户端主动关闭连接 │ │ │ │ └──────────────────────┘ │ │ │ │ │ ╔════╧═════════════════════════════════════════╧════╗ │ ║ ║ │ ║ 报文 #8FINACK客户端请求关闭 ║ │ ║ ┌────────────────────────────────────────────┐ ║ │ ║ │ Seq 1201客户端当前序列号 │ ║ │ ║ │ Ack 5001维持确认 │ ║ │ ║ │ Flags FIN1, ACK1 │ ║ │ ║ │ Window 65535接收缓冲区大小 │ ║ │ ║ │ Length 0 字节 │ ║ │ ║ └────────────────────────────────────────────┘ ║ │ ║ ║ │ ╚═══════════════════════════════════════════════════╝ │ │ │ │ 状态ESTABLISHED → FIN_WAIT_1 │ │ │ 收到 FIN状态ESTABLISHED → CLOSE_WAIT │ │ 发送 ACK 确认 │ │ │ │ ╔════╧═════════════════════════════════════════╧════╗ │ ║ ║ │ ║ 报文 #9ACK服务器确认关闭 ║ │ ║ ┌────────────────────────────────────────────┐ ║ │ ║ │ Seq 5001服务器序列号 │ ║ │ ║ │ Ack 1202确认客户端 FIN │ ║ │ ║ │ Flags ACK1 │ ║ │ ║ │ Window 65535 │ ║ │ ║ │ Length 0 字节 │ ║ │ ║ └────────────────────────────────────────────┘ ║ │ ║ ║ │ ╚═══════════════════════════════════════════════════╝ │ │ │ │ 收到 ACK状态FIN_WAIT_1 → FIN_WAIT_2 │ │ 等待服务器发送 FIN │ │ │ │ │ │ [服务器应用层处理剩余数据] │ │ [准备关闭连接] │ │ │ │ ╔════╧═════════════════════════════════════════╧════╗ │ ║ ║ │ ║ 报文 #10FINACK服务器请求关闭 ║ │ ║ ┌────────────────────────────────────────────┐ ║ │ ║ │ Seq 5001服务器序列号 │ ║ │ ║ │ Ack 1202维持确认 │ ║ │ ║ │ Flags FIN1, ACK1 │ ║ │ ║ │ Window 65535 │ ║ │ ║ │ Length 0 字节 │ ║ │ ║ └────────────────────────────────────────────┘ ║ │ ║ ║ │ ╚═══════════════════════════════════════════════════╝ │ │ │ │ │ 状态CLOSE_WAIT → LAST_ACK │ │ │ │ ╔════╧═════════════════════════════════════════╧════╗ │ ║ ║ │ ║ 报文 #11ACK客户端确认关闭 ║ │ ║ ┌────────────────────────────────────────────┐ ║ │ ║ │ Seq 1202客户端下一个序列号 │ ║ │ ║ │ Ack 5002确认服务器 FIN │ ║ │ ║ │ Flags ACK1 │ ║ │ ║ │ Window 65535接收缓冲区大小 │ ║ │ ║ │ Length 0 字节 │ ║ │ ║ └────────────────────────────────────────────┘ ║ │ ║ ║ │ ╚═══════════════════════════════════════════════════╝ │ │ │ │ 状态FIN_WAIT_2 → TIME_WAIT │ 收到 ACK状态LAST_ACK → CLOSED │ 等待 2MSL 后关闭 │ 释放资源 │ │ │ │ ┌────┴───────────────────────────────────────────┴────┐ │ │ │ │ │ 【连接完全关闭】 │ │ │ 客户端TIME_WAIT → CLOSED2MSL 后 │ │ │ 服务器已进入 CLOSED 状态 │ │ │ │ │ └──────────────────────────────────────────────────────┘二、TCP 头字段解析四次挥手部分2.1 核心字段变化表字段名图示中的值作用与含义解释Sequence Number(序列号)报文 #8: 1201报文 #11: 1202定义: 标识数据段第一个字节的序列号为什么变化:•报文 #8: 1201 之前发送了 200 字节1001-1200•报文 #11: 1202 1201 1因为 FIN 占用 1 个序列号核心规则: FIN 占用 1 个序列号Acknowledgment Number(确认号)报文 #9: 1202报文 #11: 5002定义: 期望收到的下一个字节的序列号为什么变化:•报文 #9: 1202 1201 1确认客户端 FIN•报文 #11: 5002 5001 1确认服务器 FIN关键理解: FIN 也占用 1 个序列号需要确认Flags(标志位)报文 #8: FIN1, ACK1报文 #9: ACK1报文 #10: FIN1, ACK1报文 #11: ACK1各标志位含义:•FIN: 结束标志请求关闭连接•ACK: 确认标志组合含义:•FIN1, ACK1: 关闭请求 确认•ACK1: 确认报文Window Size(窗口大小)所有报文: 65535定义: 接收方告知发送方的接收缓冲区大小注意: 本示例保持不变实际会动态变化2.2 为什么 FIN 要占用序列号┌──────────────────────────────────────────────────────────┐ │ 问题FIN 是控制标志没有数据为什么要占序列号 │ ├──────────────────────────────────────────────────────────┤ │ │ │ 答案为了可靠传输需要确认 │ │ │ │ FIN 占用 1 个序列号的原因 │ │ ├── 发送 FIN 包Seq当前序列号 │ │ ├── 接收方确认AckSeq1 │ │ └── 如果 FIN 丢失需要重传必须有唯一序列号标识 │ │ │ │ 示例报文 #8、#9、#10、#11 │ │ • 报文 #8: 客户端发送 FINSeq1201 │ │ • 报文 #9: 服务器确认Ack120212011 │ │ • 报文 #10: 服务器发送 FINSeq5001 │ │ • 报文 #11: 客户端确认Ack500250011 │ │ │ │ 证明FIN 占用了 1 个序列号 │ │ │ │ 对比普通 ACK 不占序列号 │ │ └── ACK 是纯确认不需要被确认无需序列号 │ │ │ └──────────────────────────────────────────────────────────┘三、四次挥手深度解析3.1 为什么需要四次挥手┌─────────────────────────────────────────────────────────┐ │ 问题为什么是四次而不是三次 │ ├─────────────────────────────────────────────────────────┤ │ │ │ TCP 是全双工通信 │ │ ├── 客户端 → 服务器方向 1 │ │ └── 服务器 → 客户端方向 2 │ │ │ │ 四次挥手的必要性 │ │ ├── 第一次客户端 FIN → 停止发送方向 1 │ │ ├── 第二次服务器 ACK → 确认客户端关闭 │ │ ├── 第三次服务器 FIN → 停止发送方向 2 │ │ └── 第四次客户端 ACK → 确认服务器关闭 │ │ │ │ 为什么不能合并第二、三次 │ │ ├── 服务器可能还有数据要发送 │ │ ├── 必须等应用层处理完毕 │ │ └── 不能像握手那样合并 SYNACK │ │ │ │ 特殊情况可以三次 │ │ └── 如果服务器没有数据要发送可以合并 FINACK │ │ │ └─────────────────────────────────────────────────────────┘3.2 报文详细分析报文 #8FINACK解析┌─────────────────────────────────────────────────────────┐ │ Transmission Control Protocol │ │ Sequence number: 1201 │ │ Acknowledgment number: 5001 │ │ Flags: 0x011 (FIN, ACK) │ │ .... ...1 .... .... Fin: Set │ │ .... .... 1... .... Acknowledgment: Set │ │ Window size: 65535 │ └─────────────────────────────────────────────────────────┘ 关键字段解读 ├── Seq1201: 客户端当前序列号之前发送了 1001-1200 ├── Ack5001: 维持对服务器的确认 ├── FIN1, ACK1: 请求关闭 确认 ├── 客户端状态ESTABLISHED → FIN_WAIT_1 └── 含义客户端数据发送完毕请求关闭连接报文 #9ACK 确认解析┌─────────────────────────────────────────────────────────┐ │ Transmission Control Protocol │ │ Sequence number: 5001 │ │ Acknowledgment number: 1202 │ │ Flags: 0x010 (ACK) │ │ Window size: 65535 │ └─────────────────────────────────────────────────────────┘ 关键字段解读 ├── Ack1202: 确认客户端 FIN12011 │ └── 证明FIN 占用 1 个序列号 ├── 客户端状态FIN_WAIT_1 → FIN_WAIT_2 ├── 服务器状态ESTABLISHED → CLOSE_WAIT └── 含义确认客户端关闭请求但我可能还有数据报文 #10FINACK解析┌─────────────────────────────────────────────────────────┐ │ Transmission Control Protocol │ │ Sequence number: 5001 │ │ Acknowledgment number: 1202 │ │ Flags: 0x011 (FIN, ACK) │ │ Window size: 65535 │ └─────────────────────────────────────────────────────────┘ 关键字段解读 ├── Seq5001: 服务器序列号本例中服务器未发送数据 ├── Ack1202: 维持确认 ├── FIN1, ACK1: 服务器请求关闭 ├── 服务器状态CLOSE_WAIT → LAST_ACK └── 含义服务器数据发送完毕请求关闭连接报文 #11ACK 确认解析┌─────────────────────────────────────────────────────────┐ │ Transmission Control Protocol │ │ Sequence number: 1202 │ │ Acknowledgment number: 5002 │ │ Flags: 0x010 (ACK) │ │ Window size: 65535 │ └─────────────────────────────────────────────────────────┘ 关键字段解读 ├── Seq1202: 客户端下一个序列号 ├── Ack5002: 确认服务器 FIN50011 ├── Window65535: 接收缓冲区大小保持不变 ├── 客户端状态FIN_WAIT_2 → TIME_WAIT ├── 服务器状态LAST_ACK → CLOSED └── TIME_WAIT 持续 2MSL 后 → CLOSED四、状态转换完整流程4.1 客户端状态转换ESTABLISHED │ │ 发送 FIN报文 #8 ↓ FIN_WAIT_1 │ │ 收到 ACK报文 #9 ↓ FIN_WAIT_2 │ │ 收到 FIN报文 #10 │ 发送 ACK报文 #11 ↓ TIME_WAIT │ │ 等待 2MSL ↓ CLOSED4.2 服务器状态转换ESTABLISHED │ │ 收到 FIN报文 #8 │ 发送 ACK报文 #9 ↓ CLOSE_WAIT │ │ 发送 FIN报文 #10 ↓ LAST_ACK │ │ 收到 ACK报文 #11 ↓ CLOSED4.3 状态详解┌─────────────────────────────────────────────────────────┐ │ TCP 连接关闭状态详解 │ ├─────────────────────────────────────────────────────────┤ │ │ │ FIN_WAIT_1 │ │ ├── 主动关闭方发送 FIN 后进入 │ │ ├── 等待对方的 ACK │ │ └── 可能同时等待对方的 FIN │ │ │ │ FIN_WAIT_2 │ │ ├── 收到 ACK 后进入 │ │ ├── 等待对方的 FIN │ │ └── 半关闭状态可接收数据不能发送 │ │ │ │ CLOSE_WAIT │ │ ├── 被动关闭方收到 FIN 后进入 │ │ ├── 等待应用层关闭 │ │ └── 可能还有数据要发送 │ │ │ │ LAST_ACK │ │ ├── 被动关闭方发送 FIN 后进入 │ │ ├── 等待最后的 ACK │ │ └── 收到 ACK 后立即关闭 │ │ │ │ TIME_WAIT │ │ ├── 主动关闭方收到 FIN 并发送 ACK 后进入 │ │ ├── 等待 2MSL确保最后的 ACK 到达 │ │ └── 防止旧报文干扰新连接 │ │ │ └─────────────────────────────────────────────────────────┘五、常见问题Q1: 为什么 TIME_WAIT 状态需要等待 2MSL┌─────────────────────────────────────────────────────────┐ │ MSLMaximum Segment Lifetime报文最大生存时间 │ │ │ │ RFC 793 建议 │ │ ├── MSL 2 分钟 │ │ └── 2MSL 4 分钟 │ │ │ │ 实际操作系统配置不同系统不同 │ │ ├── Linux: 60 秒2MSL 120 秒 │ │ ├── Windows: 2 分钟2MSL 240 秒 │ │ └── FreeBSD: 30 秒2MSL 60 秒 │ │ │ │ 为什么等待 2MSL │ │ │ │ 1. 保证最后的 ACK 能到达服务器 │ │ ├── 如果 ACK 丢失服务器会重传 FIN │ │ ├── 客户端在 2MSL 内可以收到重传的 FIN │ │ └── 并重新发送 ACK │ │ │ │ 2. 防止旧连接的延迟报文干扰新连接 │ │ ├── 让所有旧报文在网络中消失 │ │ └── 2MSL 足够让报文被丢弃 │ │ │ │ 3. 被动关闭方正常关闭 │ │ └── 服务器能正常进入 CLOSED 状态 │ │ │ └─────────────────────────────────────────────────────────┘Q2: TIME_WAIT 过多会有什么问题如何解决┌─────────────────────────────────────────────────────────┐ │ TIME_WAIT 过多的问题 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 1. 端口资源耗尽 │ │ ├── 每个连接占用一个端口 │ │ ├── 端口范围0-65535 │ │ └── 高并发短连接会导致端口耗尽 │ │ │ │ 2. 内存资源占用 │ │ ├── 每个连接占用内核内存 │ │ └── 大量 TIME_WAIT 占用大量内存 │ │ │ │ 解决方案 │ │ │ │ 方案 1设置 SO_REUSEADDR │ │ └── 允许重用 TIME_WAIT 状态的端口 │ │ │ │ 方案 2调整内核参数Linux │ │ ├── net.ipv4.tcp_tw_reuse 1 │ │ ├── net.ipv4.tcp_tw_recycle 1已废弃 │ │ └── net.ipv4.tcp_max_tw_buckets 调整上限 │ │ │ │ 方案 3使用长连接 │ │ └── 减少连接建立和关闭次数 │ │ │ │ 方案 4应用层连接池 │ │ └── 复用连接避免频繁关闭 │ │ │ └─────────────────────────────────────────────────────────┘Q3: 如果第四次挥手ACK丢失会怎样┌─────────────────────────────────────────────────────────┐ │ 场景报文 #11ACK丢失 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 客户端 │ │ ├── 发送 ACK 后进入 TIME_WAIT │ │ ├── 等待 2MSL │ │ └── 如果收到重传的 FIN重新发送 ACK │ │ │ │ 服务器 │ │ ├── 未收到 ACK停留在 LAST_ACK │ │ ├── 超时后重传 FIN报文 #10 │ │ ├── 重传多次后仍未收到 ACK │ │ └── 最终超时关闭发送 RST 或直接关闭 │ │ │ │ 结论 │ │ ├── TIME_WAIT 的 2MSL 机制可以处理 ACK 丢失 │ │ └── 确保被动关闭方能正常关闭 │ │ │ └─────────────────────────────────────────────────────────┘Q4: 主动关闭和被动关闭有什么区别┌─────────────────────────────────────────────────────────┐ │ 主动关闭 vs 被动关闭 │ ├─────────────────────────────────────────────────────────┤ │ │ │ 主动关闭方本例客户端 │ │ ├── 发送第一个 FIN │ │ ├── 经历FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT │ │ ├── 需要等待 2MSL │ │ └── 消耗更多资源 │ │ │ │ 被动关闭方本例服务器 │ │ ├── 收到第一个 FIN │ │ ├── 经历CLOSE_WAIT → LAST_ACK → CLOSED │ │ ├── 不需要等待 2MSL │ │ └── 收到最后的 ACK 后立即关闭 │ │ │ │ 最佳实践 │ │ ├── 服务器应尽量成为被动关闭方 │ │ ├── 让客户端承担 TIME_WAIT 的开销 │ │ └── 避免服务器资源耗尽 │ │ │ └─────────────────────────────────────────────────────────┘六、Wireshark 技巧6.1 抓包过滤器# 抓取 TCP 挥手 tcp (tcp.flags.fin 1 || tcp.flags.ack 1) # 抓取 FIN 包 tcp.flags.fin 1 # 抓取 RST 包异常关闭 tcp.flags.reset 16.2 显示过滤器# 显示四次挥手 tcp.flags.fin 1 # 显示 TIME_WAIT 状态的连接 tcp.flags.fin 1 tcp.flags.ack 1 # 显示重传的 FIN tcp.analysis.retransmission tcp.flags.fin 1