当前位置: 首页 > news >正文

【图文讲解】TCP为啥要3次握手和4次挥手?握两次手不行吗?

一、前置知识:TCP报文首部中的关键标志位

在讲解握手和挥手前,我们需要先认识几个在TCP首部中起着控制作用的关键标志位(Flags):

  • SYN (Synchronize Sequence Numbers):用作建立连接时的同步信号。

  • ACK (Acknowledgment):确认信号,用于确认收到了数据包,为1时表示确认号有效。

  • FIN (Finish):用于断开连接,表示发送方已经没有数据要传输了。

  • SEQ (Sequence Number):序列号,用来标识从TCP源端向目的端发送的字节流。

  • ack (Acknowledgment Number):确认号,即期望收到对方下一个报文段的第一个数据字节的序号。

二、TCP三次握手(建立连接)

2.1 什么是三次握手?

所谓三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。这个过程主要目的是确认双方的接收能力和发送能力是否正常指定自己的初始化序列号(ISN)以及为后续数据传输做准备

下面是三次握手的经典图示:

详细过程:

  1. 第一次握手:客户端向服务器发送一个SYN报文(SYN=1),并随机生成一个起始序列号seq=x。发送完毕后,客户端进入SYN_SENT状态。这表示客户端想要建立连接,并且告诉服务器自己的初始序号是什么。

  2. 第二次握手:服务器收到SYN报文后,如果同意连接,会回复一个SYN+ACK报文。其中,确认号ack=x+1(表示收到了客户端的SYN),并为自己随机初始化一个序列号seq=y。此时服务器进入SYN_RCVD状态。

  3. 第三次握手:客户端收到服务器的SYN+ACK报文后,需要回复一个ACK报文,其中ack=y+1表示确认收到了服务器的SYN。发送完毕后,客户端进入ESTABLISHED状态。当服务器收到这个ACK后,也进入ESTABLISHED状态,连接建立成功。

2.2 核心问题:为什么要三次握手?握两次手不行吗?

核心答案:不行。三次握手的最根本目的是为了防止“已失效的连接请求报文”突然又传送到了服务端,从而产生错误

假设一种场景:只有两次握手。

  1. 场景重现:客户端第一次发送了一个连接请求SYN报文,但是在某些网络节点中长时间滞留了。客户端因为没有收到确认,以为报文丢失了,于是重传了一个新的SYN报文,这次连接建立成功,数据传输完毕,然后正常关闭。

  2. 问题发生:此时,之前滞留的那个旧SYN报文突然“苏醒”并到达了服务器。

  3. 两次握手的后果:服务器收到这个旧的SYN报文,误以为客户端又想建立新连接,于是立即发送ACK确认报文给客户端,并进入ESTABLISHED状态,开始分配内存等资源等待客户端发送数据。

  4. 结果:但实际上,客户端早已关闭,根本不会理会这个ACK,也不会发送数据。这就导致服务器白白地维护着一个空的连接,造成了资源的浪费。这种现象也被称为“旧重复SYN的干扰”。

三次握手如何解决?
在三次握手下,服务器需要等待客户端的第三次握手ACK。在上述场景中,服务器发送了SYN+ACK(第二次握手)后,等待客户端回复。但客户端并没有发起新的连接请求,因此不会对这个过期的SYN+ACK回复ACK。服务器在超时后就会释放相关资源,避免了资源浪费。

除了防止历史连接请求,三次握手还有两个重要作用:

  • 确认双方收发能力正常

    • 第一次握手:客户端发送SYN,服务端收到。服务端得出结论:客户端的发送能力正常,自己的接收能力正常。

    • 第二次握手:服务端发送SYN+ACK,客户端收到。客户端得出结论:自己的发送和接收正常,服务端的发送和接收正常。(但此时服务端还不知道自己的发送是否正常、客户端的接收是否正常)

    • 第三次握手:客户端发送ACK,服务端收到。服务端得出结论:自己的发送能力正常,客户端的接收能力正常

  • 同步初始化序列号(ISN)
    TCP通信双方需要维护一个序号,用来保证数据的有序性和去重。三次握手的过程,就是双方互相交换初始序号(客户端seq=x,服务端seq=y)的过程。如果只有两次握手,只能保证客户端的ISN被服务端确认,服务端的ISN无法被客户端确认。

三、TCP四次挥手(断开连接)

3.1 什么是四次挥手?

所谓四次挥手,是指断开一个TCP连接时,需要客户端和服务器总共发送4个包。由于TCP连接是全双工的(即数据在两个方向上能同时传递),因此每个方向都必须单独进行关闭。

详细过程:

  1. 第一次挥手:主动关闭方(这里以客户端为例)发送一个FIN报文(FIN=1),序列号为seq=u,表示“我的数据发送完了,我想关闭连接”。发送后,客户端进入FIN_WAIT_1状态。

  2. 第二次挥手:被动关闭方(服务器)收到FIN后,回复一个ACK报文,确认号为ack=u+1。表示“我收到了你的关闭请求,但我可能还有数据要发送”。发送后,服务器进入CLOSE_WAIT状态。客户端收到ACK后,进入FIN_WAIT_2状态。此时,从客户端到服务器方向的连接已经关闭,但服务器到客户端的方向还是打开的。

  3. 第三次挥手:当服务器也没有数据要发送时,服务器会发送一个FIN报文(FIN=1)给客户端,序列号假设为seq=w,确认号依然是ack=u+1。表示“我这边数据也发完了,可以关闭了”。发送后,服务器进入LAST_ACK状态。

  4. 第四次挥手:客户端收到FIN后,回复最后一个ACK报文,确认号为ack=w+1。发送后,客户端进入TIME_WAIT状态。服务器收到这个ACK后,立即进入CLOSED状态。而客户端需要等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后,才会进入CLOSED状态。

3.2 核心问题:为什么挥手是四次?不能像握手一样合并成三次吗?

核心答案:因为TCP连接是全双工的,需要保证每个方向都能独立关闭。

  • 握手时的合并(SYN+ACK):在第二次握手时,服务端可以将SYNACK合并成一个包发送。这是因为建立连接时,服务端在收到客户端的SYN后,立即就可以决定是否同意连接,并且可以立即将自己的初始序号(SYN)带回去,不需要做额外的等待。

  • 挥手时的分离(ACK 和 FIN):在断开连接时,被动关闭方(服务器)收到FIN后,可能还有数据没有传完,不能立刻关闭Socket。

    • 所以,它只能先回复一个ACK,告诉对方“你的FIN我收到了,请稍等”。

    • 等到所有剩余数据传输完毕,应用层调用关闭Socket后,内核才会发送FIN包。

    • 这中间的ACK和FIN是有明确先后顺序且可能间隔一段时间的,因此大多数情况下无法合并,必须是四次。

特殊情况:如果服务器在收到FIN后,发现自己也没有数据要发送了(或者应用层也同时调用了关闭),那么可以将对FIN的ACK和自己的FIN合并在一个包中发送,这样四次挥手就变成了三次挥手

3.3 为什么客户端最后需要等待 2MSL(TIME_WAIT 状态)?

这是TCP协议中非常经典的一个设计,主要有两个原因:

  1. 保证最后一个ACK能被对方收到(可靠地终止TCP连接)
    客户端发送的最后一个ACK有可能在网络中丢失。如果服务器在LAST_ACK状态下超时(没收到ACK),它会重发FIN给客户端。如果客户端直接进入CLOSED状态,就收不到这个重传的FIN,自然也不会重传ACK,这样服务器就会一直处于LAST_ACK无法关闭。
    客户端等待2MSL时间,就可以接收到重传的FIN,并重新发送ACK,保证连接正常关闭。

    • MSL:报文在网络中最大的生存时间。

    • 2MSL:能保证在这个时间段内,本次连接产生的所有报文段都从网络中消失,既保证了最后一个ACK可达,也防止了它对后续连接的影响。

  2. 防止“旧连接”的延迟报文影响“新连接”
    如果没有2MSL的等待,当客户端立即用相同的IP和端口建立新连接时,网络中可能还残留着上一个连接延迟到达的数据包。这些“脏数据”可能会被新连接误认为是自己的数据而接收,造成数据错乱。等待2MSL可以确保所有旧连接的报文都在网络中消失,让新连接安全开始。

四、深入探讨:握手与挥手的疑难点

4.1 握两次手的另一个问题:无法同步双方的序列号

只有两次握手,只能保证客户端的序列号被服务器确认,但服务器自己的初始序列号无法得到客户端的确认(因为没有第三次握手的ACK)。这样,如果服务器的SYN在传输中丢失了,客户端还不知道服务器的序号是什么,后续数据传输就会错乱。

4.2 初始化序列号(ISN)为什么不固定?

ISN不能固定,否则会造成新旧连接的冲突。

  • 例子:假设ISN固定为1。客户端先建立连接,发送了10个包(序号1-10),然后断开。如果这些包被网络延迟了。接着,客户端用同样的端口建立新连接,新连接的初始序号又是1。当旧的延迟包(比如序号3)到达时,新连接就会误以为是自己的数据,导致混乱。

  • 解决方案:RFC793建议ISN与一个假的时钟绑定,每4微秒加一,直到2^32溢出。这保证了不同连接的ISN几乎不会重复。现在的实现通常在此基础上增加随机性,以防止安全攻击。

4.3 握手过程中的半连接队列与全连接队列

在三次握手中,服务端维护了两个重要的队列:

  1. 半连接队列(SYN Queue):当服务器收到客户端的SYN(第一次握手)后,会把这个连接信息放入半连接队列,此时连接状态为SYN_RCVD

  2. 全连接队列(Accept Queue):当服务器收到客户端的ACK(第三次握手)后,会把这个连接从半连接队列移到全连接队列,此时连接状态变为ESTABLISHED,等待应用程序调用accept()取走。

如果这两个队列满了,就会导致连接失败或丢包。例如,SYN Flood 攻击就是伪造大量SYN包塞满半连接队列,使得正常连接请求无法被处理。

4.4 什么是 SYN Flood 攻击(SYN洪水攻击)?

  • 原理:攻击者伪造大量不存在的IP地址,向服务器发送SYN包(第一次握手)。服务器回复SYN+ACK(第二次握手)后,由于IP是伪造的,永远等不到客户端的ACK(第三次握手)。这些半连接长时间占用SYN队列,导致正常SYN包被丢弃。

  • 现象:服务器大量连接处于SYN_RCVD状态。

  • 防御SYN Cookies是一种常用防御手段。它不在收到SYN时立即分配资源,而是通过一种特殊的算法将连接信息编码在SYN+ACK包的序号中发回去。如果收到合法的ACK,再分配资源,从而避免资源耗尽。

五、总结

  • 3次握手:核心是防止历史连接请求同步初始序号。握手时,服务端的SYN和ACK可以合并,所以是3次。

  • 4次挥手:核心是TCP的全双工特性。被动关闭方需要时间处理剩余数据,导致ACK和FIN必须分开发送,所以通常是4次(特殊情况下可优化为3次)。

  • 2MSL(TIME_WAIT):是TCP可靠性的重要保障,用于保证最后一个ACK可达和防止旧包干扰新连接。

http://www.jsqmd.com/news/390303/

相关文章:

  • 高辐射率涂层原理、工艺与选型指南
  • 利用FLAC3D软件求解不同应力释放系数下的围岩特征曲线
  • 《军储与重点仓储空间级轨迹建模与异常停留识别平台》——统一坐标输出轨迹证据链 × 长时停留风险评分
  • Doris在零售行业的应用:实时销售分析系统
  • 商用硅基涂料(如HiE-Coat、Pyromark)
  • nodejs+vue3智慧教育学习笔记系统
  • 小型公司持续集成工具 Jenkins 实践:从零搭建到高效运维
  • 协同过滤算法Nodejs+vue3的旅游景点推荐系统 商家
  • 无人机倾斜摄影内业全流程
  • VR华夏神舟——沉浸式遨游太空,解锁航天科普新体验 - 指南
  • 一个可用的SQLite方言类
  • 数据中台在大数据领域的用户体验设计
  • 股市赚钱学概论:为什么股市能赚钱
  • AI和大模型之一介绍
  • 突破性进展:如何实现AI系统的有效短期记忆?
  • BISHI57 最大公因数与最小公倍数
  • 掌握大数据领域RabbitMQ的配置文件管理
  • 背包专题 - # 奶牛展览(Cow Exhibition)
  • [Spring测试]TestRestTemplate
  • 探索大数据领域数据科学的时间序列分析
  • 大数据挖掘中的隐私保护与伦理问题探讨
  • 第六章 从“能用”到“能交付”的关键一刀:偏好对齐(Preference Alignment)数据工程
  • 大模型在哲学论证推理中的逻辑一致性评估
  • 玩转 Java8 中的 Stream:从零认识与实战详解
  • 完整教程:大蜂智能科技携手拯救HMI:重新定义气调包装设备的智能交互体验
  • windows从源码安装python版本paddleocr3.4.0
  • Nodejs+vue3的电商管理系统 购物商城优惠卷
  • CppCon 2025 学习: Umpire: Portable Memory Management for High-Performance Computing Applications
  • rtos问题
  • Netty、Kafka 中的零拷贝技术到底有多牛?