不懂 TCP 三次握手、四次挥手?面试官:回去等通知吧!

网络 网络管理
TCP的三次握手和四次挥手的来龙去脉应该都清楚了。TCP 之所以这样设计,是为了实现可靠的网络通信:三次握手确保双向通信可靠、序列号同步以及避免历史连接;四次挥手则确保数据传输完整、连接优雅关闭,以及防止旧连接影响新连接。

🌟 开篇:你以为你懂 TCP?其实……

来,问几个简单的问题:

1️⃣ 为什么是"三次"握手?两次不行吗?四次多余吗?

2️⃣ 为什么是"四次"挥手?两次不行吗?

3️⃣ 你知道 TCP 握手和挥手的整个流程吗?每一步到底是在干嘛吗?

如果这些问题你还不能完全确定,那你一定要继续往下看。今天用最直白、最好玩的方式,带你彻底搞懂 TCP 的握手与挥手!

🎭 场景:夜店搭讪 vs 网络连接

TCP 连接就像夜店搭讪(建立连接)和礼貌告别(断开连接)。搞不好,分分钟变成社死现场!让我们跟着小明的故事,一步步理解这个过程。

💏 第一幕:三次握手 - "搭讪的艺术"

小明在夜店看到小红,但他可不会傻傻地直接喊"做我女朋友!"(除非想被当成神经病)。他需要一个稳妥的三步走计划:

[小明](客户端)          [小红](服务器)
      |----SYN----->|  小明:"嗨,可以认识一下吗?"
      |<--SYN-ACK---|  小红:"可以呀,你是谁?"
      |----ACK----->|  小明:"我是小明,程序员,喜欢猫..."

🔍 完整的三次握手过程是这样的:

图片图片

来看看TCP三次握手是怎么玩的:

客户端和服务器开始时都是"关门"状态(CLOSED)。这就像两个人要开始聊天:

  • 客户端先发个招呼(SYN=1, seq=x),说"嘿,能聊聊吗?",然后等在那里(SYN_SENT状态)。服务器这时候是准备听的状态(LISTEN 状态)。
  • 服务器收到后回应说(SYN=1, ACK=1, seq=y, ack=x+1)"好啊,我听见你了",自己也进入等待状态(SYN_RCVD状态)。
  • 客户端最后确认一下(ACK=1, ack=y+1),相当于说"太好了,那我们开始聊吧"。这时候双方都进入了可以正常聊天的状态(ESTABLISHED状态)。

从技术角度看,这个过程不仅确认了双方都能收发数据,还完成了初始序列号的同步,建立了可靠的通信基础。

🤔 等等!为啥非要三次?两次不行吗?

🎯 原因一:建立"双向通信"(能收能发)

想象你在KTV包厢点歌:

第一次:
你:(按麦克风按钮)"喂喂?"
(确认你的麦克风能发声)

第二次:
前台:(通过音响)"听到了,你要点什么歌?"
(确认音响能放声,你能听到)

第三次:
你:(通过麦克风)"我要点《野狼disco》!"
(确认你能听到音响声,整个通信链路都通了)

如果只有两次对话:

  • 你按了麦克风
  • 前台回应了
  • 但你可能没听到前台的回应
  • 结果你在那自顾自地点歌
  • 前台:???到底要点啥

📝 在TCP协议中,这个双向通信确认过程是这样的:

第一次: SYN=1, seq=x
客户端 ------------------> 服务器
(我能发消息,我的序号是x)

第二次: SYN=1, ACK=1, seq=y, ack=x+1
客户端 <------------------ 服务器
(我能收能发,收到你的x号消息)

第三次: ACK=1, ack=y+1
客户端 ------------------> 服务器
(我也能收能发,收到你的y号消息)

这就是TCP中的"全双工通信"建立过程:

  1. 第一次握手:客户端证明自己能发数据
  2. 第二次握手:服务器证明自己能收能发数据
  3. 第三次握手:客户端证明自己能收数据

就像KTV包厢的设备测试:

  • "喂喂?"(证明麦克风能用)
  • "听到了,你能听到吗?"(证明音响能放,工作人员能听)
  • "能听到!"(证明整个系统都OK)

只有这样才能确保:

  • 客户端和服务器都能发送数据
  • 双方都能接收对方的数据
  • 通信链路是完全双向通畅的 🤔 等等!为啥非要三次握手?两次不行吗?

🔄 原因二:同步双方的序号(确认彼此的初始序列号)

如果只有两次握手:

客户端                         服务器
   |                            |
   |--------SYN seq=100-------->| 给你我的序号100 
   |                            |
   |<--SYN+ACK seq=200,ack=101--| 好的,我用200   
   |                            |

问题:服务器不知道客户端是否真的收到了它的200

  • 两次握手只能保证服务器收到了客户端的初始序号100
  • 但无法保证客户端收到了服务器的初始序号200
  • 双方的初始序号必须都能被对方确认,两次握手做不到这一点

所以必须要第三次握手:

客户端                          服务器
   |                            |
   |--SYN seq=100-------------->|  给你我的序号100
   |<--SYN+ACK seq=200,ack=101--|  好的,我用200
   |--ACK ack=201-------------->|  我知道你用200了

就像两个人约定暗号:

  • 小明:我用暗号100
  • 小红:收到你的100,我用暗号200
  • 小明:好,我也收到你的200了

通过三次握手:

  • 服务器确认收到了客户端的序号
  • 客户端也确认收到了服务器的序号
  • 双方都知道对方的初始序号,可以开始可靠通信

这就是为什么必须是三次握手,因为只有这样才能确保双方的初始序号都被对方成功确认!

😱 原因三:防止过期消息(解决穿越)

[星期五晚上]
小明用手机打开交友APP:
> 9:00 - 发送好友申请,但信号不好没发出去
> 9:05 - 重新发送申请,这次成功了!开始愉快聊天
> 9:30 - 突然!之前卡住的申请终于发出去了...

如果没有第三次确认,会怎样?

  1. 服务器收到 9:00 的迟到申请
  2. 回应说:"好啊,我们开始聊天吧"
  3. 但这时用户都聊了半小时了!
  4. 服务器还傻傻地为这个旧请求准备资源
  5. 结果:浪费服务器资源,还可能打断正在进行的聊天。

有了第三次握手,就不会出现这种情况:

[9:30] 迟到的申请终于到达服务器
服务器:"收到你的申请啦!"
客户端:(发现是半小时前的旧消息)不回应
服务器:(没收到回应,知道是旧消息,直接忽略)

📝 在TCP协议中,这个过程实际是这样的:

[过期的SYN请求]
客户端  ----迟到的SYN(seq=x)--------->  服务器
客户端  <---SYN+ACK(seq=y,ack=x+1)---  服务器
客户端  (发现是旧请求,不回应ACK)   
服务器  (等待超时,关闭半连接)

这就涉及到TCP的几个重要机制:

1.半连接队列(SYN Queue)

  • 服务器收到SYN后,会创建一个半连接
  • 如果没收到第三次ACK,这个连接就会被丢弃
  • 避免了服务器资源被旧请求占用

2.连接超时机制

  • 服务器会设置一个超时时间(通常是几十秒)
  • 在这个时间内没收到ACK就清理掉半连接
  • 防止半连接队列被占满

3.序列号验证

  • 客户端收到 SYN+ACK 时会验证序列号
  • 如果是过期请求,就不会发送ACK
  • 服务器因此能识别出过期连接

这就是为什么TCP需要三次握手,而不是两次的原因:

  • 防止历史连接的意外建立
  • 保护服务器资源不被浪费
  • 确保建立的都是有效连接

再想想上面的例子:

  • SYN就像小明的好友申请
  • SYN+ACK是服务器的回应
  • 没有第三次ACK,就相当于小明没确认
  • 服务器就知道这是旧的申请,可以安全地忽略它

这个设计巧妙地解决了网络中的"延迟消息"问题,是TCP协议最精华的部分之一!

🤔 还有,为啥非要三次?四次不行吗?

让我们继续用KTV点歌的例子:

[三次握手]
你:喂喂?(能发声)
前台:听到了!(能收能发)
你:好的!(整个链路通了)

[如果是四次]
你:喂喂?(能发声)
前台:听到了!(能收到)
前台:你要点歌吗?(能发声)
你:好的!(确认)
这样看出问题了吗?

第二次和第三次握手其实可以合并
前台说"听到了"的同时就能问"要点歌吗"
没必要分开说,那样反而浪费时间

技术角度来说:

服务器收到 SYN 后:
- 已经知道客户端能发送数据
- 自己能收到数据
- 可以直接在 ACK 中带上自己的 SYN
- 不需要分两次发送

简单说:

  • 三次刚刚好:确认双方都能收发
  • 两次不够:无法确认客户端能收
  • 四次多余:把能合并的分开了

记住:TCP设计的原则是既要保证可靠性,又要追求效率!

💡 小结

三次握手不是为了复杂,而是为了:

  1. 建立"双向通信"(双方都能收能发)
  2. 同步双方的序列号(保证通信顺序)
  3. 防止历史连接的意外建立(避免"延迟消息")
  4. 确保双方都准备好了才开始通信

记住:每一次握手都有它存在的道理,不是技术人员闲着没事干设计的,而是为了解决实际的网络通信问题!

🎭 第二幕:四次挥手 - "告别的艺术"

🌟 继续我们的夜店故事

还记得小明和小红吗?经过三次握手,他们已经聊得很开心了。但是夜店总要散场,到了说再见的时候。可是,体面人的告别可不是说走就走!

🎭 四次挥手是怎么回事?

分别的是时候,小明不能突然消失,得体面地说再见。我们看看这个过程:

[小明]                    [小红]
   |                        |
   |--"我该走了"(FIN)-----> |     第一次:表达想走的意思
   |<--"等等,我说完"(ACK)-- |    第二次:先别急,还有话说
   |<--"好了说完了"(FIN)--- |     第三次:我也说完了,再见
   |--"好,拜拜"(ACK)-----> |     第四次:最后的告别

📝 这在TCP协议中实际是这样的:

客户端                                           服务器
  |     1. FIN=1, seq=x            |
  | ------------------------------>| 第一次:客户端表示要关闭连接
  |     2. ACK=1,seq=y,ack=x+1     |
  |  <-----------------------------| 第二次:服务器确认,但自己还有数据要发
  |                                |
  |     (服务器发送剩余数据)          |
  |  3. FIN=1,ACK=1,seq=z,ack=x+1  |
  |  <-----------------------------| 第三次:服务器发完数据,也准备关闭了
  |  4. ACK=1,seq=x+1,ack=z+1   |
  | -----------------------------> | 第四次:客户端最后确认
  | TIME_WAIT (2MSL等待...)         |

🤔 为什么要四次?两次不行吗?

想象一下这个尴尬的场景:

[如果只有两次挥手]
小明:时间不早了,我要走了!
小红:好的再见!
(双方立马分别)

问题是:小红正要给小明微信号,结果小明已经离开了...  // 一个本该成功的搭讪,因为太急着说再见而失败

为什么这样不行?

  1. 小明说"我要走了"只是表示他不发消息了
  2. 但小红可能还有话要说(比如发微信号)
  3. 如果立即分别,小红的话就来不及说了。

技术角度来说:

[两次挥手的问题]
客户端                                  服务器
   |---我不发消息了---------------->|
   |<--好的再见,连接关闭-----------|
   |                               | 服务器的数据发不出去了!

所以必须要四次:

[优雅的告别]
小明:我要走了,不说话了
小红:等等,我还有话要说
      (把微信号给小明)
小红:好了,我说完了
小明:收到,拜拜!

这就是为什么需要四次挥手:

  • 让双方都能说完该说的话
  • 避免重要信息发不出去
  • 确保完整优雅地断开连接

📝 深入理解四次挥手

先来看看 TCP 四次挥手的详细图解:

图片图片

1️⃣ 第一次挥手:主动说再见

小明:"时间不早了,我该走了"

技术视角:

第一次挥手:FIN=1, seq=x
客户端 -----------------> 服务器
(客户端的数据发完了,准备关闭)
  • 客户端发送 FIN 包,序号为x
  • 进入FIN_WAIT_1状态
  • 表示客户端没有数据要发了

2️⃣ 第二次挥手:先别急

小红:"等等,我还有话说..."

技术视角:

第二次挥手:ACK=1,seq=y,ack=x+1
客户端 <----------------------- 服务器(服务器确认,好的,我知道了,但我还有数据要发)
  • 服务器发送ACK,序号为y,确认号为x+1
  • 进入 CLOSE_WAIT 状态,表示服务器可能还有数据要发送
  • 当客户端收到 ACK 包时,客户端进入 FIN_WAIT_2 状态

3️⃣ 第三次挥手:处理完了

小红:"好了,联系方式给你了,我也该说再见了"

技术视角:

第三次挥手:FIN=1, seq=z, ack=x+1
客户端 <------------------------- 服务器(我的数据也发完了,准备关闭)
  • 服务器发送FIN包,序号为 z,确认号是x+1
  • 进入LAST_ACK状态
  • 表示服务器的数据也发送完毕

4️⃣ 第四次挥手:最后道别

小明:"好的,拜拜!"(但还会在门口等一会儿)

技术视角:

第四次挥手:ACK=1,seq=x+1,ack=z+1
客户端 --------------------------> 服务器
(好的,我知道了,可以关闭了)
  • 客户端发送ACK,序列号为x+1,确认号为z+1
  • 进入 TIME_WAIT 状态
  • 等待 2MSL 后才真正关闭

最后,双方都进入 CLOSED 状态。

🤓 为什么要等待2MSL?

想象这个场景:

小明说完"拜拜"后,没有立即离开
而是在夜店门口等了一会儿,因为:
1. 万一小红没听清他说的"拜拜"

技术角度解释:

客户端                                           服务器
 ...
  | 4. ACK=1,seq=x+1,ack=z+1 --------------->  | 第四次:客户端最后确认
  | TIME_WAIT (2MSL等待...)                    |
  1. MSL是数据包的最大生存时间(一般是几十秒)
  2. 等待 2MSL 是为了:
  • 确保最后的ACK能到达服务器
  • 等待可能的FIN重传
  • 防止旧连接的数据包影响新连接

旧连接的数据包影响新连接是什么意思?

让我通过一个具体的例子来解释"防止旧连接的数据包影响新连接"。

假设没有2MSL等待时间:

客户端A                                        服务器
   | 1. 断开连接                                 |
   | 2. 马上重新建立新连接                         |
   |                        迷路的数据包 ------>  | 
   |                     (来自刚才的旧连接)      |
   | 结果:服务器会把旧数据包当成新连接的数据!        |

   # 假设客户端A和服务器的 ip 地址 和 端口 保持不变,即:四元组保持不变

就像这样的场景:

[夜店场景]
21:00 - 小明和小红说再见,立刻离开
21:01 - 小明又和小红相约夜店,可能有重要的事...
21:02 - 突然!服务员送来小明之前给小红点的酒...
小红:???(搞不清这是之前的单,还是新的单)

有了2MSL等待:

客户端A                                          服务器
   | 1. 断开连接                                  |
   | 2. 等待2MSL...                              |
   |    (足够让之前连接的所有数据包都消失)            |
   | 3. 这时候建立新连接                           |
   |    (不会有旧数据包来捣乱)                      |

简单说:

  • 2MSL的等待就是让之前的所有互动(数据包)都完全结束,不会让旧数据包来影响新连接。

💡 四次挥手的精髓

在生活中:

  • 双方都要把话说完
  • 确保对方听到了告别
  • 给对方充足的处理时间
  • 不能突然消失

在TCP中:

  • 确保数据完整传输
  • 双方都能正常关闭
  • 防止数据丢失
  • 资源能被正确释放

记住:四次挥手的每一步都是必要的,是为了让网络通信更可靠!

总结:

通过这篇文章,TCP的三次握手和四次挥手的来龙去脉应该都清楚了。TCP 之所以这样设计,是为了实现可靠的网络通信:三次握手确保双向通信可靠、序列号同步以及避免历史连接;四次挥手则确保数据传输完整、连接优雅关闭,以及防止旧连接影响新连接。看似繁琐的每一次握手和挥手,其实都是为了解决具体的网络通信问题,体现了 TCP 协议的可靠性和严谨性。一旦理解了这些设计背后的原因,也就理解了 TCP 协议的精髓。

责任编辑:武晓燕 来源: 跟着小康学编程
相关推荐

2023-10-24 15:22:09

TCPUDP

2024-01-12 08:23:11

TCPACK服务器

2022-08-28 20:35:52

三次握手四次挥手TCP

2019-06-12 11:26:37

TCP三次握手四次挥手

2015-10-13 09:42:52

TCP网络协议

2023-10-28 09:07:57

TCP面试三次握手

2021-05-18 12:27:40

TCP控制协议

2019-02-01 09:38:16

2021-01-29 06:11:08

TCP通信三次握手

2021-07-03 17:47:25

TCP控制协议

2019-04-11 10:10:01

2017-09-25 21:27:07

TCP协议数据链

2024-05-07 08:15:33

TCP四次挥手三次握手

2020-02-17 10:10:43

TCP三次握手四次挥手

2015-11-09 09:58:56

2022-11-17 10:20:49

TCP三次握手四次挥手

2020-06-29 14:50:47

TCP状态ACK

2021-05-28 09:08:20

TCP连接序列号

2019-01-25 09:21:30

2014-09-19 09:46:46

TCPIP
点赞
收藏

51CTO技术栈公众号