三次握手四次挥手详解

TCP 是面向连接的协议,这意味着在进行数据传输之前,通信的两端需要先建立一个连接,这个连接的建立是通过三次握手完成的。当通信结束时,双方需要进行四次挥手来关闭连接。今天就来详细介绍一下三次握手和四次挥手的过程。

TCP协议简述

认识三次握手和四次挥手之前,我们要先了解一下 TCP 协议及其主要特点。
TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议。以下是 TCP 协议的一些主要特点:

  • 面向连接:TCP 是一种面向连接的协议,通信的双方在传送数据之前必须先建立连接。连接的建立过程采用三次握手,确保双方都能够理解对方的状态。

  • 可靠性:TCP 提供可靠的数据传输服务,通过使用序列号、确认和重传机制来确保数据的正确性和完整性。如果数据包丢失或损坏,TCP 会负责重传丢失的数据,从而保证数据的可靠传输。

  • 面向字节流:TCP 是基于字节流的协议,它不关心应用层的数据单元是什么,而是将数据看作是一个连续的字节流。这使得 TCP 可以适应不同应用层协议传输不同大小的数据块。

  • 全双工通信:TCP 连接是全双工的,这意味着通信的双方可以独立地发送和接收数据。这使得双方能够同时进行双向通信,实现更灵活的数据交换。

  • 流量控制:TCP 使用流量控制机制来防止发送方发送速度过快导致接收方无法处理的情况。通过动态调整发送方的发送速率,TCP 可以适应网络的变化,确保数据的有序传输。

  • 拥塞控制:TCP 具有拥塞控制机制,它通过动态调整发送速率来避免网络拥塞的发生。当网络拥塞时,TCP 会减缓发送速率,从而减轻网络负担,提高整体性能。

  • 三次握手和四次挥手:TCP 连接的建立和关闭过程分别采用了三次握手和四次挥手的机制。三次握手用于连接的建立,而四次挥手用于连接的关闭,确保连接的安全可靠地建立和关闭。

总体而言,TCP协议在可靠性和连接管理方面的特性使其成为互联网中最常用的传输层协议之一。

TCP包首部

一篇详细介绍 TCP 的好文,图片示例来源:
https://zhuanlan.zhihu.com/p/108504297

网络中传输的数据包通常由两部分构成:

  • 首部(Header):首部是数据包的元数据部分,包含了一系列的控制信息,用于指导数据包的传输、路由和处理。不同层次的协议有各自的首部结构,包含了与该层协议相关的信息。例如,在 TCP/IP 协议栈中,数据链路层有以太网帧首部,网络层有 IP 首部,传输层有 TCP 或 UDP 首部。

  • 有效载荷(Payload):有效载荷是数据包中携带的实际数据部分。这是应用层产生的数据,需要在网络中传输到目的地。有效载荷的内容取决于上层协议,例如,在 HTTP 通信中,有效载荷可能是 HTML 网页、图片、文本等。

这两部分的组合构成了完整的数据包。首部提供了关于数据包的元信息,使得网络设备能够正确地处理和路由数据。有效载荷包含了需要传输的实际数据,是通信的主体。

一图示例网络中传输的数据包的构成:
数据包

这样的数据包会在网络中传输,经过各个网络设备,最终到达目的地,然后被目的设备的协议栈逐层解析,提取出有效载荷,并交付给上层应用。所以看到首部,就能够了解该协议必要的信息以及所要处理的数据。
那么首部主要由哪些部分构成,每部分代表什么呢?下图是 TCP 头部的规范定义,定义了 TCP 协议如何读取和解析数据。
TCP首部
我们今天的重点并不是详细分析每一位的构成,我们只需要大概了解一下这些代表的都是什么,以此来更清晰地认识 TCP 的三次握手和四次挥手的过程。

  • 源端口号 (Source Port) 和目的端口号 (Destination Port): 指明发送方的端口号和接收方的端口号。(源IP,源端口号)+ (目地IP,目的端口号)用于确定唯一连接。

  • 序列号 (Sequence Number): 缩写为 seq,用于对发送的数据进行编号。序列号表示 TCP 报文中第一个数据字节的序号,通过这个来确认发送的数据有序。

  • 确认号 (Acknowledgment Number): 缩写为 ack,仅在 ACK 标志位被设置时有效,表示期望接收到的下一个序列号,用于确认已成功接收到的数据。是对上一次 seq 序号做出的确认号,等于收到的 TCP 报文段的序号 seq 加1。

  • 数据偏移 (Data Offset): 表示 TCP 头部的长度,以32比特为单位。由于 TCP 头部长度可变,这个字段用于指示头部的长度,从而确定数据部分的起始位置。

  • 标志位 (Flags): 用于指示 TCP 包的状态或功能,包括:

    • URG (Urgent): 表示紧急指针字段有效。
    • ACK (Acknowledgment): 表示确认号字段有效。
    • PSH (Push): 表示接收方应该立即将数据交给应用层而不进行排队。
    • RST (Reset): 表示重置连接。
    • SYN (Synchronize): 用于发起一个连接。
    • FIN (Finish): 表示发送方已经完成数据发送。
  • 窗口大小 (Window Size): 表示发送方愿意接收的数据量的大小。用于流量控制,接收方通过这个字段告知发送方当前它还能接收多少数据。

  • 校验和 (Checksum): 用于检测 TCP 首部和数据的传输是否发生错误。

  • 紧急指针 (Urgent Pointer): 仅在 URG 标志位被设置时有效。用于指示紧急数据的末尾位置。

  • 选项 (Options): 可选字段,用于在 TCP 头部中包含一些可选的信息。例如,最大报文长度(MSS)、窗口扩大因子、时间戳等。

三次握手

三次握手详细过程

三次握手是 TCP 协议用于建立连接的过程。在这个过程中,通信的两端需要交换一系列的控制信息,确保双方都已准备好进行数据传输。这个过程需要客户端和服务器总共发送3个报文,所以叫三次握手。以下是三次握手的详细过程:
三次握手图示

  • 第一次握手
    客户端首先向服务器发送一个 TCP 报文,其中设置 SYN(同步)标志位为1,表示客户端请求建立连接。客户端还会选择一个随机的初始序列号 seq = x。发送完毕后,客户端进入 SYN_SENT 状态,等待服务器端确认。
  • 第二次握手
    服务器处于持续的 Listen 状态,当收到客户端的数据包后,由 SYN = 1 知道客户端想要建立连接,会回复一个TCP报文,其中设置 SYNACK(确认)标志位都为1,ack = x + 1,表明下次期望收到的 seq 是 x + 1,并且随机生成一个 seq = y。将该数据包发送给客户端后,服务器端进入 SYN_RCVD 状态。
  • 第三次握手
    客户端收到确认后,检查 ack 是否为 x + 1,ACK是否为1,如果正确则将标志位 ACK 置为1,ack = y + 1seq = x + 1,然后将该数据包发送给服务器端,客户端进入 ESTABLISHED 状态。服务器收到报文段后,检查 ack 是否为 y + 1,ACK 是否为1,如果正确则连接建立成功,服务器端进入 ESTABLISHED 状态。

完成了上述三个步骤后,TCP 连接就建立起来了。现在,双方可以开始进行数据的传输。

三次握手常见问题

三次握手的目的是什么

三次握手是为了建立可靠的通信连接,确认双方的发送与接收能力是否正常,同时初始化双方的序列号。

为什么是三次握手,不是两次或者四次

  • 为什么不是两次握手:
    tcp 是全双工通信,两次握手只能确定单向数据链路是可以通信的,并不能保证反向的通信正常。
  • 为什么不是两次握手:
    服务端把回复 ACK 和发送 SYN 的过程合并了,提高了握手效率,三次就可以完成通信建立。

详细通俗地来说,为什么是三次握手要结合三次握手的目的:确认双方发送与接收能力是否正常。

  1. 第一次握手:
    客户端说:“喂,我想和你通信。”
    服务端听到了,知道客户端的发送能力正常,自己的接收能力正常。
  2. 第二次握手:
    服务端回应:“好的,我也想和你通信。”
    客户端听到了,知道服务端的发送和接收能力都正常,自己的发送能力和接受能力也正常。但服务端此时并不知道客户端的接收能力是否正常。
  3. 第三次握手:
    客户端再说:“好的,我们可以开始通信了。”
    服务端听到了,知道客户端的发送和接收能力都正常,而且客户端也听到了,于是服务端也确认了自己的发送和接收能力都正常。

通过这三次握手,双方确认了彼此的通信能力,确保了连接的可靠性。这个过程是为了避免因网络延迟或其他问题而导致的不确定性,从而建立一个稳定的连接。

三次握手在很大程度上可以防止因网络中滞留的数据包导致的问题,确保了连接的可靠性。具体来说,这个过程可以防止以下两种主要情况:

  1. 防止过期的连接请求:
    如果只有两次握手,客户端发送了连接请求(SYN),但这个请求在网络中滞留,可能由于延迟、拥塞等原因导致服务端迟迟未收到。客户端可能会在超时后重传连接请求,但这时如果之前的连接请求已经到达服务端,服务端会认为这是一个新的连接请求,从而导致产生过期的连接。
  2. 防止半开连接问题:如果只有两次握手,服务端收到了连接请求(SYN),然后发送了确认和自己的连接请求(SYN + ACK)。但是,由于网络问题,客户端未能收到服务端的确认,于是客户端不知道服务端是否成功接收了连接请求,可能会再次发送连接请求。如果此时服务端已经成功建立了连接,那么客户端的新连接请求会被服务端认为是一个新的连接,导致半开连接问题。

通过引入第三次握手,可以解决上述问题。第三次握手中,客户端向服务端发送一个确认(ACK),表示它确实已经收到了服务端的连接确认。这样一来,就确保了双方对连接的状态有了一致的认知,从而避免了滞留导致的连接问题。

半连接队列(Half-Open Connection Queue)

半连接队列是在服务器接收到客户端的 SYN(同步)请求后,但尚未完成三次握手建立连接时所处的状态。在这个状态下,服务器会将这些还未建立连接的请求暂时存储在半连接队列中。原因和用途如下:

  • 流量控制:当服务器接收到大量连接请求时,可以将这些请求放入半连接队列,逐渐处理,避免瞬间连接数过多导致资源耗尽。
  • 连接处理:服务器通过逐个处理半连接队列中的请求,进行第二次和第三次握手,最终建立完整的连接。

还有一个全连接队列,已经完成三次握手,建立起连接的就会放在全连接队列中,如果队列满了就有可能会出现丢包现象。

三次握手的过程中能携带数据吗

在 TCP 的三次握手过程中,第一次和第二次握手通常不携带数据,而第三次握手则可以携带数据。这设计上的选择主要考虑了网络安全和可靠性的因素。

  • 第一次握手和第二次握手不携带数据的原因:
    • 防止攻击: 如果允许在初始握手阶段携带大量数据,攻击者可以通过发送大量 SYN 报文,占用服务器资源,导致服务器容易受到拒绝服务(DoS)攻击。
    • 资源限制:在初始阶段,服务器并不知道客户端的可靠性和接收能力,因此不愿意接受大量数据,以防止浪费资源。
  • 第三次握手可以携带数据的原因:
    • 连接已建立:在第三次握手时,客户端已经成功与服务器建立了连接,而且服务器也已经确认了客户端的连接请求。此时,双方对彼此的状态有了更准确的了解。
    • 可靠性已确认:第三次握手表示连接已经建立,而且客户端和服务器都确认了对方的可靠性。因此,可以在这个时候携带数据进行传输。

总体而言,TCP 的设计追求在连接建立后才进行数据的传输,以确保连接的可靠性和安全性。允许在握手过程中携带数据可能会引入不必要的风险和开销。

SYN-FLOOD泛洪攻击

泛洪攻击是利用三次握手中最后一次的确认握手来发起攻击。攻击者伪造源地址,向目标服务器发送大量的 SYN 请求,服务器在接收到这些伪造的 SYN 请求后,会为每个请求创建一个半连接(半开连接),确认 ACK 发送到被伪造的地址,等待客户端发送确认 ACK。而真正地址的客户端并没有发起请求,所以不会理服务器的确认 ACK,服务器会重传和等待,造成资源的浪费。如果攻击者发送大量的伪造请求,困难导致服务器上的资源(例如,内存和连接表)耗尽。当服务器的资源耗尽时,它将无法再接受新的合法连接请求,导致服务拒绝,甚至可能导致服务器崩溃。

四次挥手

四次挥手详细过程

四次挥手是 TCP 连接的关闭过程,用于在数据传输完成后,双方安全地关闭连接。这个过程需要客户端和服务器总共发送四个报文,所以叫四次挥手。以下是四次挥手的详细过程:
四次握手图示

挥手请求可以是客户端,也可以是服务器发起的,我们此处假设是客户端发起。

  • 第一次挥手
    客户端首先向服务器发送一个带有 FIN(Finish) 标志位的 TCP 报文,表示客户端不再发送数据。设置序列号 seq = u,此时,客户端进入 FIN_WAIT_1 状态,等待服务器的确认。
  • 第二次挥手
    服务器收到客户端的 FIN 后,会发送一个带有 ACK = 1 的 TCP 报文给客户端,表示收到了客户端的关闭请求,同时设置序列号 seq = vack = u + 1。服务器进入 CLOSE_WAIT 状态,等待应用层的数据处理完成。此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入 FIN_WAIT2 状态,等待服务端发出的连接释放报文段。
  • 第三次挥手
    一段时间后,服务器的应用层完成数据处理,服务器会发送一个带有 FIN = 1,ACK = 1 的 TCP 报文给客户端,同时设置 seq = wack = u + 1, 表示服务器也准备关闭连接。服务器进入 LAST_ACK 状态,等待客户端的确认。
  • 第四次挥手
    客户端收到服务端的连接释放报文段后,发出确认报文段,设置 ACK = 1,seq= u + 1,ack = w + 1,客户端进入 TIME_WAIT(时间等待)状态。服务器在收到客户端的确认后,进入 CLOSED 状态,表示连接已经完全关闭。而客户端需要经过时间等待计时器设置的时间 2MSL 后,客户端才进入 CLOSED 状态,表示连接已经完全关闭。

在四次挥手的过程中,每个阶段都有特定的状态和标志位,确保双方都有足够的时间来完成数据的传输和处理。这种设计可以防止在连接关闭时可能出现的延迟数据导致的问题。

四次挥手常见问题

为什么是四次挥手,而不是三次或者五次

在建立连接时,服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN + ACK 报文。ACK 报文用于应答,SYN 报文用于同步。
但是关闭连接时,服务端收到客户端的 FIN 报文时,意味着客户端想断开连接,不再向服务端发送数据了。但服务器可能还有没处理完的,需要向客户端发送的数据,所以只能先回一个 ACK 报文段,表示 “你的请求我知道了,但我还不能断开连接,我还有数据没发送完。” 等服务器处理完应用数据后,才会向客户端发送 FIN 报文段,表明服务器可以断开连接了。

四次挥手的设计是由于 TCP 连接全双工、半关闭的性质。
全双工:通信的双方能够同时进行双向的数据传输,即可以同时发送和接收数据。这种通信方式允许通信的两端在同一时间内既可以发送信息又可以接收信息,就像是一条双向的道路,数据可以在两个方向上同时传递。所以断开连接需要保证通信的两端都断开。
半关闭:半关闭指的是在一个全双工的连接中,当其中一方结束了它的数据发送时,会发送一个 FIN 来终止这一方向的连接,发送 FIN 只意味着这一方结束了它的数据发送,但仍然可以接收来自对方的数据。所以需要两方都发送了 FIN 并被确认,连接才会断开。

为什么 FIN 和 ACK 报文要分两次发送

如果将服务端向客户端发送的报文的第二次挥手和第三次挥手合并为一次挥手,可能会导致服务端在发送回执后立即发送断开请求,进而造成服务端有数据没有全部发送至客户端。分两次发送的原因包括:

  • 数据传输的完整性:在关闭连接时,服务端可能仍有未发送完的数据需要传输。如果将第二次挥手和第三次挥手合并,服务端就可能在发送完 ACK 后立即发送 FIN,导致数据没有完全传输完毕。

  • 等待应用层处理:在服务端发送 FIN 之前,需要等待应用层的数据处理完成,以避免数据的丢失或截断。如果合并为一次挥手,就无法区分这两个阶段。

  • 确保正确的关闭流程:通过分为两次挥手,能够确保服务端在关闭连接时能够逐步通知客户端,先通知客户端它不再发送数据(第二次挥手),然后再通知客户端它已经准备关闭连接,但可能还有未发送完的数据需要传输(第三次挥手)。

将第二次挥手和第三次挥手分开,使得连接关闭过程更为细致,确保了数据的完整性,同时也考虑了网络和应用层处理的一些延迟和异步性。

CLOSE_WAIT 状态存在的意义是什么

在 CLOSE_WAIT 状态下,服务器已经收到了来自客户端的连接关闭请求(FIN),并发送了确认(ACK)。服务器在等待应用层处理完数据之后才会继续关闭连接,这个状态的存在有以下主要目的和意义:

  • 等待应用层处理:在服务器端,CLOSE_WAIT 状态表示服务器应用层正在处理连接上接收到的数据。这个状态允许服务器应用层有足够的时间来处理连接上的数据,确保数据的完整性和正确性。
  • 保证数据完整性:在关闭连接之前,服务器需要等待应用层完成对接收到的数据的处理。这样可以确保服务器不会在数据处理尚未完成时就立即关闭连接,从而保证了数据的完整性。
  • 避免数据丢失:如果服务器在 CLOSE_WAIT 状态时立即关闭连接,可能会导致尚未处理完的数据丢失。等待应用层处理完成后再关闭连接可以避免这种情况。
  • 防止连接池资源浪费:在高并发环境下,服务器可能同时处理多个连接。等待 CLOSE_WAIT 状态的连接完成后再关闭连接,可以避免连接池中的资源被浪费,确保资源得到有效释放。

总体来说,CLOSE_WAIT 状态的存在是为了保证在关闭连接之前,服务器能够合理地等待应用层对数据的处理,以确保数据的完整性和正确性。这是TCP协议设计中考虑到的一种机制,用于处理连接的优雅关闭。

TIME_WAIT状态存在的意义是什么

TIME_WAIT 状态存在的主要目的是防止客户端的关闭确认丢失,导致服务器无法进入连接关闭状态;以及确保在关闭连接后的一段时间内,本连接持续时间内产生的所有报文都从网络中消失,以避免新的连接受到旧连接请求报文的影响。这个状态的重要性包括以下几点:

  • 确保可靠的连接关闭:在正常情况下,TIME_WAIT 状态的等待时间(2倍的 MSL,即两倍的最大报文生存时间)能够确保在这段时间内,网络中的所有可能延迟到达的报文都能够被丢弃,从而避免对新的连接产生不良影响。
  • 防止混淆:在 TIME_WAIT 状态等待期间,确保旧的连接请求报文已经从网络中消失,避免了新的连接因为端口重用而受到旧连接请求的混淆。
  • 处理可能的重传:如果客户端的关闭确认(ACK)在传输途中丢失,服务端会重传关闭请求(FIN)。如果客户端没有等待 2*MSL 时间而直接进入了 CLOSED 状态,客户端就会收不到服务端再次发送的断开连接的请求报文,导致服务端无法进入 CLOSED 状态。所以设置 TIME_WAIT 状态,服务器重传时,客户端正好在该状态等待。这样,客户端能够正确地接收到服务端的重传,并响应关闭。
  • 避免端口冲突:在 TIME_WAIT 状态结束之前,确保旧连接释放的端口不被新的连接使用,防止端口重用引起的混淆。

总的来说,TIME_WAIT 状态的存在是为了确保连接关闭的可靠性,并处理在关闭过程中可能发生的各种情况,以提高整个系统的健壮性。