type
status
date
slug
summary
tags
category
icon
password

硬核图解:TCP 三次握手与四次挥手全解析

在计算机网络的世界里,TCP(传输控制协议)是互联网的基石。无论是刷视频、玩游戏还是打开网页,TCP 都在底层默默地保障数据的可靠传输
很多人背得下“三次握手、四次挥手”,但往往经不住面试官的深挖:“为什么握手是三次而不是两次?”“那个大写的 ACK 和小写的 ack 到底啥关系?”
本文将通过 Mermaid 时序图,结合底层原理,带你彻底搞懂这些细节。

🛠️ 第一部分:读懂 TCP 报文头——ACK 与 ack 的区别

在深入流程之前,我们必须先解决一个让无数初学者晕头转向的问题:大写 ACK 和小写 ack 有什么区别?
TCP 报文头中包含了很多字段,其中两个极其相似但功能完全不同的概念:

1. 标志位 (Flags) —— 大写的 ACK

  • 性质:这是一个 1 bit 的开关(标志位)
  • 位置:位于 TCP 头部控制位(Control Bits)中。
  • 作用:它像一个“开关”。
    • ACK = 1 时,表示报文头部的 确认号 (ack) 字段是有效的。
    • ACK = 0 时,确认号字段无效(通常只有连接刚开始建立的第一个 SYN 包,ACK 才是 0)。
  • 一句话记忆ACK 是一个动作指令,告诉对方“我要确认收货了”。

2. 确认号 (Acknowledgment Number) —— 小写的 ack

  • 性质:这是一个 32 bit 的数值
  • 位置:位于 TCP 头部。
  • 作用:它表示期望收到对方下一个报文段的第一个数据字节的序号。
    • 如果你收到了对方发来的序号 100~200 的数据,你的 ack 应该是 201
  • 一句话记忆ack 是具体的收货单号,告诉对方“201 之前的我都收到了,请发 201 及其之后的数据给我”。
总结:在握手和挥手过程中,除了第一个 SYN 包,后续所有包的 ACK 标志位都必须置为 1,而 ack 数值 则随着数据的传输不断累加。

🤝 第二部分:三次握手 (Three-Way Handshake)

TCP 建立连接需要三次交互。

📊 流程图解

Code snippet

❓ 核心面试题:为什么是三次握手?不是两次?不是四次?

这是一个经典的博弈逻辑问题。
1. 为什么不是“两次”?(防止历史连接的干扰)
  • 场景:假设客户端发出的第一个 SYN 包在网络中滞留了(没丢,只是慢了)。客户端以为丢了,重发了第二个 SYN。
  • 如果是两次握手
    • 服务端收到了滞留的旧 SYN,立刻回复 ACK 并建立连接
    • 客户端收到 ACK 后,发现这是个旧请求的回复,直接丢弃,不理服务端。
    • 后果:服务端以为连接建好了,傻傻地等着客户端发数据,导致资源死锁/浪费
  • 如果是三次握手
    • 服务端收到旧 SYN,回复 SYN+ACK。
    • 客户端收到后,根据序列号判断出这是个旧连接,回复 RST(重置)报文。
    • 服务端收到 RST,关闭连接,避免了资源浪费。
  • 结论三次握手的主要目的是防止“已失效的连接请求”突然又传送到服务端,导致错误建立连接。
2. 为什么不是“四次”?(效率最优解)
  • 理论上,双方建立连接可以分为 4 步:
      1. A 发送 SYN
      1. B 回复 ACK (确认收到 A 的 SYN)
      1. B 发送 SYN (B 也想连 A)
      1. A 回复 ACK (确认收到 B 的 SYN)
  • 优化:TCP 协议允许将第 2 步和第 3 步合并。即 B 在确认 A 的同时,把自己的 SYN 也捎带过去(SYN+ACK 包)。
  • 结论:四次没必要,三次刚刚好。
3. 为什么不是“二十次”?
  • 工程设计讲究**“奥卡姆剃刀”**原则——如无必要,勿增实体。三次已经完美解决了“双方确认对方发送/接收能力”以及“同步序列号”的问题。再多就是浪费网络带宽和时间延迟。

👋 第三部分:四次挥手 (Four-Way Handshake)

TCP 连接是全双工的,这意味着数据传输是双向的。断开连接时,需要两个方向分别关闭。

📊 流程图解

Code snippet

❓ 核心面试题:为什么挥手需要四次?

  • 握手是“合并”的,挥手为什么要“分开”?
    • 在握手时,服务端在 LISTEN 状态下,可以立即将 SYN 和 ACK 合并发送。
    • 但在挥手时,客户端发来 FIN,只代表客户端没数据发了
    • 此时,服务端可能还有数据需要处理和发送(比如正在处理数据库查询)。所以服务端必须先回一个 ACK ("我知道你想走了"),等自己事情忙完了,再发一个 FIN ("我也好了,走吧")。
    • 结论:因为半关闭 (Half-Close) 的存在,ACK 和 FIN 通常无法合并,必须分两次发。

💣 第四部分:高频面试死穴 —— TIME_WAIT 状态

如果面试官问 TCP,90% 的概率会问到 TIME_WAIT

1. 什么是 TIME_WAIT?

从上面的图可以看到,主动关闭连接的一方(通常是 Client),在发出最后一个 ACK 后,不会立即关闭,而是进入 TIME_WAIT 状态,并停留 2MSL(Maximum Segment Lifetime,最长报文段寿命,Linux 默认约 60秒)的时间。

2. 为什么需要 TIME_WAIT?(必须背诵)

这里有两个核心原因:
  • 原因 A:防止旧报文干扰新连接
    • 如果不等待,客户端立刻关闭并立刻重启一个新的连接(用同样的 IP 和端口)。
    • 此时,上一个连接中迷路在网络里的“旧数据包”突然到达了,新连接可能会混淆,把旧数据当成新数据接收,导致数据错乱。
    • 等待 2MSL 可以保证本次连接产生的所有报文都在网络中消失。
  • 原因 B:确保被动关闭方能收到最后的 ACK
    • 假设客户端发完第 4 次挥手的 ACK 后直接跑路 (CLOSED)。
    • 如果这个 ACK 在半路丢了,服务端收不到确认,就会触发超时重传,重发第三次挥手的 FIN。
    • 此时客户端已经关闭了,收到重发的 FIN 会一脸懵逼,回复 RST(报错),导致服务端认为连接异常关闭。
    • 等待的意义:如果 ACK 丢了,客户端还能在 TIME_WAIT 状态下收到重发的 FIN,并重新发送 ACK,确保服务端正常关闭。

3. 服务器出现大量 TIME_WAIT 怎么办?

  • 现象:高并发场景下,如果服务器主动关闭连接(作为主动方),会产生大量 TIME_WAIT,占用端口资源。
  • 解决
    • 调整内核参数 net.ipv4.tcp_tw_reuse = 1(允许复用 TIME_WAIT socket)。
    • 尽量让客户端主动关闭连接(Web 服务器通常是被动方,不容易产生这个问题)。

📝 总结 CheckList

阶段
动作
核心知识点
握手
三次
防止历史连接复活;ACK标志是动作,ack数字是单号。
数据
传输
seq 标记数据位置,ack 标记收到位置。
挥手
四次
全双工通道,需要双向分别关闭;FIN 表示发送结束。
结束
TIME_WAIT
主动关闭方进入;等待 2MSL;为防旧包干扰和保底最后的 ACK。
希望这篇文章能帮你构建起完整的 TCP 知识体系!
Redis 分布式缓存架构深度解析:策略模式、一致性模型与高可用性工程实践TCP三次握手与四次挥手
Loading...
Ximou Zhao
Ximou Zhao
不要被敌人的气势汹汹所吓倒,不要被尚能忍耐的困难所沮丧,不要被一时的挫折所灰心,道路是曲折的,前途是光明的,黑暗即将过去,曙光就在眼前,有利的条件和主动的恢复,产生于再坚持一下的努力之中。