技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTNET

跟着动画来学习 TCP 三次握手和四次挥手

所属馆藏
NET Protocol
文件格式
Markdown
原始路径
NET Protocol/33-跟着动画来学习TCP三次握手和四次挥手
本文目录5 个章节
  1. TCP 三次握手
  2. TCP 数据传输
  3. TCP 四次挥手
  4. 总结
  5. 参考资料

跟着动画来学习 TCP 三次握手和四次挥手

Category(分类): NET Protocol Status: 未知

TCP 三次握手和四次挥手是面试中最常见的考点之一。很多读者都知道三次和四次,但是如果问得深入一点,往往无法作出准确回答。

本篇尝试使用动画和生活中的例子来讲解这个知识点,帮助读者更简单地理解 TCP 连接建立、数据传输和连接关闭的过程。

本文中的“招手”“说话”等内容是帮助理解的类比。实际 TCP 通信使用的是 TCP 报文段、序列号、确认号和状态机。

TCP 三次握手

TCP 三次握手就好比两个人在街上隔着 50 米看见了对方,但是因为雾霾等原因不能 100% 确认,所以要通过招手的方式相互确认对方是否认识自己。

TCP 三次握手动画

张三首先向李四招手(SYN),表示自己想建立连接,并告诉李四自己准备使用的初始序列号。

李四看到张三向自己招手后,需要同时完成两件事:一是确认已经收到张三的招手,二是向张三发出自己的招手。因此,李四会在一个 TCP 报文段中同时设置 SYNACK 标志位,即发送 SYN+ACK,而不是先发送一个 ACK、再发送一个 SYN

张三看到李四的 SYN+ACK 后,再发送一个 ACK,表示自己已经收到了李四的招手。至此,双方都确认了对方能够正常收发数据,连接建立完成,进入 ESTABLISHED 状态。

TCP 三次握手示意图

如果把李四的动作拆成两个逻辑动作,那么它相当于先确认张三的招手,再向张三招手;TCP 把这两个逻辑动作合并到了同一个 SYN+ACK 报文段中,所以实际只需要三个报文段:

客户端                                      服务端
SYN-SENT  -------- SYN ------------------>  LISTEN → SYN-RECEIVED
ESTABLISHED <------ SYN+ACK --------------  SYN-RECEIVED
ESTABLISHED -------- ACK ---------------->  ESTABLISHED

三次握手的具体过程

设客户端的初始序列号为 x,服务端的初始序列号为 y

步骤发送方向TCP 报文段主要含义
1客户端 → 服务端SYN,序列号为 x客户端请求建立连接,并同步自己的初始序列号
2服务端 → 客户端SYN+ACK,序列号为 y,确认号为 x+1服务端确认收到了客户端的 SYN,并同步自己的初始序列号
3客户端 → 服务端ACK,确认号为 y+1客户端确认收到了服务端的 SYN+ACK

SYNFIN 都会占用一个序列号,因此服务端确认客户端的 SYN 时使用 x+1,客户端确认服务端的 SYN 时使用 y+1

三次握手并不只是为了“打三次招呼”,它还用于:

  1. 同步双方的初始序列号;
  2. 让双方确认两个方向都具备收发能力;
  3. 降低历史重复 SYN 报文导致错误建立连接的可能性。

握手过程中的 TCP 状态

握手尚未完成时,常见的两个状态是 SYN-SENTSYN-RECEIVED。更准确地说,它们是“握手未完成状态”,而不是简单地把两个状态都称为“半打开状态”。

  • SYN-SENT:主动打开方已经发送 SYN,正在等待对方的响应。
  • SYN-RECEIVED:已经收到对方的 SYN,并发送了 SYN+ACK,正在等待对方最后的 ACK。

在通常的客户端—服务器模型中,客户端执行主动打开,服务器执行被动打开。但 TCP 也支持同时打开的情况,因此不能绝对地认为客户端一定是主动打开方、服务器一定是被动打开方。

另外,SYN 是 TCP 报文段中的一个控制标志位;日常也常说“SYN 报文”,更准确的说法是 “SYN 报文段”。

TCP 数据传输

TCP 数据传输就是两个人隔空对话,差了一点距离,所以需要对方对已经收到的内容进行确认。

TCP 数据传输动画

张三喊了一句话(data),李四的 TCP 协议栈收到并校验数据后,会通过 ACK 告诉张三自己已经收到哪些字节。

这里的 ACK 只表示接收端的 TCP 协议栈已经接收并确认了相应序号的数据,并不表示对方的业务程序已经读取或处理了这些数据。数据是否真正被业务程序使用,还取决于接收端应用程序什么时候调用读取接口。

如果张三喊了一句话,过了一段时间还没有收到李四的确认,张三就会根据重传定时器判断报文可能丢失,并重新发送,这就是 TCP 重传。TCP 还可能根据重复 ACK 等情况进行快速重传。

也有可能李四已经收到了张三的话,但是李四发出的 ACK 在网络中丢失了,以至于张三没有收到确认。张三无法直接判断究竟是自己的数据丢失了,还是李四的 ACK 丢失了,因此可能会重新发送数据。

既然会重传,李四的 TCP 协议栈就可能收到同一段序列号对应的数据多次。TCP 会根据序列号识别重复数据,并在协议栈内部完成去重、排序和确认,用户层通常不会看到同一份网络重传数据两次。

不过,TCP 只负责网络传输层面的可靠性。如果应用程序自己重复发送了同一个业务请求,TCP 并不知道这两个请求在业务上是否相同,也不会替应用程序去重。应用层仍然需要设计消息边界、请求幂等性和业务重试策略。

TCP 数据确认动画

张三可以向李四喊话,同样李四也可以向张三喊话,因为 TCP 连接是全双工的,双方都可以主动发起数据传输。双方发送的数据分别拥有自己的序列号和确认号。

无论是哪一方发送数据,接收方的 TCP 协议栈都会在适当的时候发送 ACK。ACK 可能单独发送,也可能和反向发送的数据一起携带;接收方还可以使用累计确认,一次 ACK 确认此前连续收到的多个字节。

延迟 ACK 和累计确认

张三可能是个高射炮,一连说了很多句话。这时候李四不必每听到一句话就立即回复,可以使用延迟 ACK 和累计确认来减少 ACK 报文数量。

但这并不是说李四一定要等到八句话全部说完才确认。通常情况下,接收端应至少每收到两个完整的 TCP 报文段确认一次,并且 ACK 不能被无限延迟;RFC 9293 要求 ACK 延迟小于 0.5 秒。收到乱序报文、填补缺口的报文或可能触发快速重传的报文时,通常还应尽快确认。

TCP 窗口

张三也不能一次性说太多话,李四的接收缓冲区短时间可能无法容纳太多数据。TCP 使用窗口机制控制发送方能够同时发送、但尚未收到确认的数据量。

这里需要区分两个概念:

  • 接收窗口(rwnd:接收方根据自己的接收缓冲区空间通告给发送方,用于流量控制,表示接收方还能接收多少数据。
  • 拥塞窗口(cwnd:发送方根据网络拥塞情况动态调整,用于拥塞控制。

实际能够在网络中同时传输、尚未确认的数据量通常受 min(rwnd, cwnd) 限制。因此,TCP 窗口不是简单协商出来的固定“发送和接收速率”。窗口扩大选项可以在握手时协商,但接收窗口和拥塞窗口在传输过程中都可能变化。

TCP 的有序字节流

网络环境的数据交互同人类之间的对话还要复杂一些,它可能存在数据包乱序的现象。同一个 TCP 连接中的不同 IP 数据报可能因为路由、负载均衡或网络排队等原因出现不同的到达顺序。

TCP 会根据序列号在协议栈内部缓存乱序数据,并在缺失的数据到达后重新组装,向用户层提供有序的字节流。如果前面的数据丢失,后面的数据即使已经到达,也可能暂时不会交付给应用程序。

需要注意,TCP 保证的是字节流的顺序,不保留应用层的消息边界。发送方调用多次 send,接收方不一定会按照相同的次数和边界调用 read 就能得到对应的数据,因此应用层通常需要自己设计消息分帧协议。

TCP 四次挥手

TCP 断开连接的过程和建立连接的过程比较类似。正常关闭时,连接的两个方向需要分别关闭,因此通常会出现四个报文段:张三挥手(FIN)——李四确认(ACK)——李四挥手(FIN)——张三确认(ACK)。在已经建立的连接中,这些报文段通常还会携带 ACK 标志位。

TCP 四次挥手动画

半关闭状态

之所以通常需要四个报文段,是因为 TCP 存在半关闭状态,也就是两个方向可以分别关闭。

张三已经挥了手,可是人还没有完全走,只是不再向李四发送数据,但是仍然可以继续接收李四发送的数据。李四收到张三的 FIN 后,先用 ACK 确认收到;如果李四还有数据要发送,就可以继续发送。等李四也不再发送数据时,再向张三发送 FIN。张三收到后回复 ACK,连接才完成正常关闭。

因此,服务端收到客户端 FIN 后进入 CLOSE-WAIT,并不代表连接立即彻底关闭;它还需要等待应用程序完成剩余工作并主动关闭自己的发送方向。如果应用程序迟迟不关闭,连接可能长时间停留在 CLOSE-WAIT

正常四次挥手过程

步骤发送方向报文段主动关闭方状态被动关闭方状态
1张三 → 李四FINFIN-WAIT-1收到后进入 CLOSE-WAIT
2李四 → 张三ACK收到后进入 FIN-WAIT-2CLOSE-WAIT
3李四 → 张三FIN收到后进入 TIME-WAIT发送后进入 LAST-ACK
4张三 → 李四ACKTIME-WAIT收到后进入 CLOSED

TCP 四次挥手示意图

TIME-WAIT 状态

上面有一个非常特殊的状态 TIME-WAIT。在通常的主动关闭场景中,主动关闭的一方收到对方的 FIN、回复最后一个 ACK 后进入这个状态;同时关闭等特殊场景下,双方都可能进入 TIME-WAIT

TCP 规范要求 TIME-WAIT 持续 2 × MSL(Maximum Segment Lifetime,最长报文段寿命)。RFC 9293 在规范中将 MSL 取为 2 分钟,因此传统教材经常写成 4 分钟。但 MSL 是一种工程取值,具体系统实现可能采用不同的 TIME-WAIT 时长,不能把 4 分钟理解成所有操作系统都固定使用的时间。

TIME-WAIT 主要有两个作用:

  1. 确保最后的 ACK 有机会被重新发送:如果对方没有收到最后的 ACK,对方会重传 FIN。处于 TIME-WAIT 的一方收到重传的 FIN 后,会重新发送 ACK,并重新启动 2 × MSL 计时器。
  2. 避免旧连接的延迟报文干扰新连接:在旧连接的四元组(源 IP、源端口、目的 IP、目的端口)被重新使用前,给网络中的旧报文留出足够的消退时间,避免它们被误认为是新连接的数据。

因此,不能简单理解为“TIME-WAIT 会把这段时间内所有残留报文立即丢弃”。TCP 仍然会按照序列号和状态机检查到达的报文,不符合当前连接要求的报文会被丢弃或按协议处理;如果收到对端重传的 FIN,则需要回复 ACK。

TIME-WAIT 保留的是内核中的 TCP 连接控制状态和连接四元组,并不意味着本地端口在任何情况下都绝对不能复用。是否能够复用,还与新的连接四元组、操作系统策略以及 SO_REUSEADDR 等套接字选项有关。应用层的 socket 文件描述符通常已经关闭,但内核仍可能暂时保留该连接的状态。

MSL 的含义是 Maximum Segment Lifetime,即“最长报文段寿命”。至于为什么使用 2 × MSL,可以简单理解为:既要给旧报文消退留时间,也要给对端发现最后 ACK 丢失并重传 FIN 留出时间。

三次挥手和其他关闭情况

四次挥手并不总是严格出现四个报文段。如果李四收到张三的 FIN 后已经没有数据要发送,并且应用程序马上关闭连接,那么李四可以把确认和自己的 FIN 合并到一个 FIN+ACK 报文段中,这时通常就是三个报文段:

张三 → 李四:FIN
李四 → 张三:FIN+ACK
张三 → 李四:ACK

在这种情况下,张三可能从 FIN-WAIT-1 直接进入 TIME-WAIT,跳过 FIN-WAIT-2。但这需要对方的 FIN 同时确认张三此前发送的 FIN;如果 FIN 到达时张三自己的 FIN 还没有被确认,状态转换可能不同,例如进入 CLOSING

如果使用 RST 强制重置连接,则属于异常关闭,不是正常的四次挥手过程。

总结

  • 三次握手的实际报文段是:SYN → SYN+ACK → ACK
  • TCP 是全双工的,连接的两个方向可以独立传输和关闭。
  • ACK 确认的是 TCP 协议栈收到的数据,不等于应用程序已经处理数据。
  • TCP 提供有序字节流,不保留应用层消息边界。
  • 四次挥手通常是:FIN → ACK → FIN → ACK,但 ACK 和 FIN 在某些情况下可以合并。
  • TIME-WAIT 的规范时长是 2 × MSL,4 分钟只是采用 2 分钟 MSL 时的传统计算结果。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS