最近要找实习,今天背了久违的面经,写个 TCP 三次握手四次挥手的短篇吧。
TCP 协议是为了解决这样一件事情:在一个底层是“尽力而为”级别服务质量的网络中,如何通过自己的努力实现 100% 可靠的数据传输呢?尽管实际的网络状况可能很棒,但是假设它很不可靠,我们就可以对问题建模了。
首先来定义什么是不可靠的网络,以及什么是(被 TCP 大手塑造的)可靠数据传输:
- 在网络中,每个数据包到达目的地的时间是不确定的;相对于发送,可能是乱序的;可能路途中会被丢弃,从而永远无法到达。
- TCP 假设自己在这样一种不可靠的环境中工作,但经过自己的努力,TCP 服务的用户看到的包是按照正确顺序接收的,且只要第 x 个包被正确接收,那前 x-1 个包必定是完整正确按顺序接收的。
- 当网络差到极点的时候,TCP 协议可能无法成功发送和接收任何数据,但它永远不会提供混乱的、损坏的数据。换句话说,TCP 可能 fail to transmit,但不会 make you confused.
一个最简单的方法是:建立编号。发送方给发送的数据流从 0 开始按字节编号,接收方源源不断地回复自己收到了哪些编号(确认标志,ACK)。当接收方收到了跳跃的编号时,就命令发送方重传。这样就能保证数据是正确的顺序,且没有任何丢失。
在真实世界中,TCP 协议是全双工的——通信的双方既是发送者,也是接收者。ACK 标志被和待发送的数据打包在一起发出。
在连接建立时,客户端与服务器会进行三次握手。这是一个老生常谈的话题,但我今天会写一点新的见解。
首先来看三次握手的基本流程:

- 客户端发送 SYN,同时提供自己的起始编号 x。
- 服务端回复 SYN + ACK,确认 x 的同时,提供自己的起始编号 y。
- 客户端回复 ACK,确认 y;这个包可以携带数据。
有些面试官会问:
为什么不是 2 次握手?为什么不是 4 次握手?
为什么你要问这些牛魔问题
一种“标准答案”是说三次握手才能分别确认 x 和 y,两次不够,四次太多。但我认为这没有触及本质——从直觉上 x 和 y 都应该是 0,都是 0 了难道就不需要三次握手了吗?真实世界中不选 0 而选两个随机数是出于安全等其他因素的考量,跟我们这个议题是无关的。
想象你自己是参与通信的一方,站在你的第一视角,你如何确认自己“被对方听到”了呢?
你说:“喂!A 呼叫 B”(SYN)
你听:“嘿!B 收到了 A 的呼叫”(ACK)
这样一来一回,你就确认了对方接受信息正常(他没收到的话,就不可能回答你),也确认了对方发送信息正常(我都已经收到他发的消息了,还不正常吗)。
同理,B 本应 也给 A 发一个 SYN,然后等 A 回一个 ACK 呢。这样两个来回,其实直觉上就是四次握手才对。
然而,B 发送的 SYN 可以和 B 回复的 ACK 绑在一起发给 A。这就让四次握手变成了三次。
我们再看看连接关闭时的流程:

是不是很符合直觉!
- A:【巴拉巴拉巴拉】,我说完了(FIN)
- B:我知道了。(ACK)
- B:【巴拉巴拉巴拉】,我说完了(FIN)
- A:我知道了。(ACK)
经过前面的铺垫,一个自然的问题是:这次怎么不合并了?为了回答这个问题,让我们看看连接建立与连接关闭的关键区别:
连接建立时,双方都是“干净”的,B 的动作完全可以预期,它回复 ACK 之后下一步一定是发送自己的 SYN,没有任何其他可能。但在连接关闭时,B 的状态是不可预期的,它可能还存在一些没有处理完的数据,想稍后发送呢。这时候就要先回复一下 ACK,然后自己在(经过一个不确定的时间后)从容地发送自己的 FIN。