【计算机网络】TCP 协议——详解三次握手与四次挥手

文章目录

  • 一、引言
  • 二、TCP
    • [1、TCP 的数据格式](#1、TCP 的数据格式)
    • [2、TCP 的三次握手](#2、TCP 的三次握手)
    • [3、TCP 的四次挥手](#3、TCP 的四次挥手)
    • [4、TCP 的全双工通信](#4、TCP 的全双工通信)
  • [三、TCP 的状态转换](#三、TCP 的状态转换)
    • [1、TCP 连接的建立(三次握手)状态](#1、TCP 连接的建立(三次握手)状态)
    • [2、TCP 连接的终止(四次挥手)状态](#2、TCP 连接的终止(四次挥手)状态)
    • [3、TCP 异常情况](#3、TCP 异常情况)

一、引言

TCP与UDP的区别相当大。它充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费(由于UDP没有连接控制,所以即使对端从一开始就不存在或中途退出网络,数据包还是能够发送出去。(当ICMP错误返回时,有时也实现了不再发送的机制。)) 。根据TCP的这些机制,在IP这种无连接的网络上也能够实现高可靠性的通信。

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

所谓"字节流"是指一种不间断的、顺序传输的数据结构,可以把它比作水管中的水流。在TCP通信中,虽然发送的数据可以保证按顺序到达接收方,但这些数据会以连续的方式流动,没有明确的分隔。例如,当发送端的应用程序使用TCP发送10次100字节的消息,接收端的应用程序可能会一次性收到一个1000字节的连续数据,而不会以独立的100字节消息形式显示。

因此,在TCP通信中,为了确保接收端能够正确理解每一条消息的边界,发送端的应用程序通常需要在消息中设置一个表示长度或分隔的字段。这个字段用于告诉接收端如何拆分和解析数据流,确保每一条消息能被正确解读。这种机制对于确保TCP数据流中的完整性和消息边界识别尤为重要。

TCP 的特点:

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的:基于校验和应答重发机制保证传输的可靠性。无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:传输的数据是无结构的字节流,基于字节流的服务没有字节序问题的困扰。用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。
  • 全双工传输:各主机 TCP 协议以全双工的方式进行数据流交换。
  • 缓存传输:缓冲传输可以延迟传送应用层的数据,允许将应用程序所需要传送的数据积攒到一定数量才进行集中的发送。
  • 流量控制:TCP协议的滑动窗口机制,支持主机间的端到端的流量控制。

如何理解传输层对数据的封装和解包?内核如何处理?

操作系统内可以同时存在多个收到的报文(这个报文还没来得及处理,可能刚拷贝到传输层的接收缓冲区,甚至还可能没有交给传输层而在数据链路层)。

OS内一定会收到大量还没来得及处理的报文。那么OS要不要对这些的暂未处理的,但是已经收到的报文进行管理?要。如何管理?对其进行抽象,抽象为数据结构。

在操作系统中,处理网络数据的关键部分是网络层和传输层的协议栈。对于UDP和TCP等协议,接收到的网络报文在处理之前需要临时存储和管理。这是通过一个称为sk_buff(socket buffer)的结构来实现的。

sk_buff 是 Linux 内核中处理网络数据包的核心组件。它提供了一种统一的方式来存储和管理网络数据包,使得不同层次的网络协议栈可以高效地协同工作。它不仅管理实际的数据,还管理所有相关的元数据,使得内核能够正确地处理、转发、路由和传输网络数据包。

  1. 传输层对数据的封装和解包

    • 封装:在传输层(TCP或UDP),应用层的数据被分段或打包,附加上传输层的头部信息。TCP会加入序列号、确认号等信息,确保数据可靠传输;UDP则直接附加简单的头部,进行快速、无连接的传输。最终,封装后的"数据段(TCP)"或"数据报(UDP)"会被传递到网络层。
    • 解包:当数据到达目标设备时,数据包会逆向通过各层协议。传输层负责去掉传输层的头部,处理相应的传输层任务(如重新排序、流量控制等),并将解包后的数据交给应用层。
  2. 内核如何处理数据包:在操作系统内核中,网络数据的处理是通过网络协议栈完成的,关键步骤包括:

    • 数据包的接收:当数据报文从网络接口到达操作系统时,它首先会被临时存储在内存中,还没来得及处理的数据会在较低层(数据链路层)等待。

      • 管理大量未处理的报文 :由于操作系统可能同时收到大量的数据报文,它需要对这些未处理的数据进行有效管理。Linux内核使用一种叫做**sk_buff**(socket buffer)的数据结构来管理这些报文。

      • sk_buff 的作用 :sk_buff是Linux内核中的核心数据结构,用于存储和管理从网络接口接收到的每一个数据包。它不仅存储实际的数据,还包含元数据(如数据包的长度、协议类型等),以便在各个网络协议栈层之间协同工作。通过sk_buff,内核可以有效地处理、转发、路由和传输数据包。

      • 多层处理 :sk_buff帮助操作系统内核将数据包传递给适当的传输层协议(如TCP、UDP),然后在传输层进一步处理,最终交给应用程序。

也就是说,Linux内核使用sk_buff来管理未处理的数据包 ,确保协议栈的不同层次能够高效协同工作,处理大量的网络流量。sk_buff(Socket Buffer )结构体作为 Linux 内核网络栈中用于管理和处理网络数据包的核心数据结构。我们结合 sk_buff 理解解包和分用的过程:

  • 解包 :当一个网络数据包被接收到时,它被封装在一个 sk_buff 结构中。网络栈使用 sk_buff 中的指针(如 network_header 和 transport_header)来解析不同协议层的头部,并提取相应的信息。
  • 分用 :sk_buff 中存储的头部信息(如 IP 地址、协议类型、端口号)被用来确定数据包的目的地。根据这些信息,系统将 sk_buff 传递给对应的处理函数或应用程序。

它通常在以下几种情况下形成:

  1. 数据包接收时 :当网络接口卡(NIC)接收到一个数据包时,NIC 的驱动程序会创建一个 sk_buff 结构来保存该数据包的数据和元数据。这个过程通常包括从 NIC 的接收缓冲区复制数据到 sk_buff 结构中。
  2. 数据包发送时 :当应用程序通过系统调用(如 sendto 或 send)发送数据时,传输层协议(如 TCP 或 UDP)会创建一个或多个 sk_buff 结构来保存数据。随后,这些 sk_buff 被传递到网络层,最终通过 NIC 发送出去。

二、TCP

1、TCP 的数据格式

TCP 在 IP 协议的基础上进行传输数据,TCP数据的报文格式如下:

源端口号和目的端口号 不再赘述。点击此处理解端口号

序列号 (Sequence Number ):32位,表示分配给TCP包的编号。序列号用于标志应用程序从 TCP 的发送端到接收端发送的字节流。当 TCP 开始连接的时候,发送一个序列号给接收端,连接成功后,这个序列号作为初始序列号(ISN,Initial Sequence Number)。建立连接成功后将按照字节的大小进行递增。用于数据包的排序和数据的重组。

  • 当TCP开始建立连接时,客户端和服务端需要交换初始序列号。这个过程通过所谓的"三次握手"来完成:
    • 第一次握手:客户端向服务端发送一个SYN报文段,报文段中包含客户端选择的初始序列号。此时,客户端的序列号用来标记数据传输的起始位置。
    • 第二次握手:服务端收到SYN报文段后,发送一个SYN_ACK报文段,确认它已收到客户端的初始序列号,同时服务端自己也生成一个初始序列号,并将其发送给客户端。
    • 第三次握手:客户端收到服务端的SYN_ACK报文后,向服务端发送ACK报文,确认已收到服务端的序列号。至此,连接正式建立。

后文会详细介绍三次握手。

确认号 (Acknowledgment Number):32位,仅在ACK位设置为1时有效,表示期望接收的数据段的下一个字节的序号。通过确认号,接收方告知发送方已经成功收到哪些数据。

也就是说,发送方对发送的首字节进行了编号,当接收方成功接收后,发送回接收成功的序列号+1 表示确认,发送方再次发送的时候从确认号开始。

序列号和确认号的具体使用如下:

  • 序列号 :
    • 由发送方(例如主机A)在TCP报头中设置。
    • 表示该TCP报文段中携带的数据的第一个字节的序列号。
    • 序号用于确保数据的有序传输,接收方(例如主机B)可以根据序号正确地重组接收到的数据。
  • 确认序号 :
    • 由接收方(例如主机B)在TCP报头中设置。
    • 表示接收方期望从发送方收到的下一个TCP报文段的数据的第一个字节的序列号。
    • 确认序号用于确认已成功接收的数据,并且告诉发送方可以发送下一个数据序列。

例如,当主机A向主机B发送数据时,它会指定一个序号。主机B在接收到数据后,会发送一个确认应答(ACK),在ACK报文中包含确认序号,以告知主机A它期望接收的下一个数据字节的序列号。

数据偏移 (Data Offset):4位,也称为头部长度,表示 TCP 报文段首部的长度,单位是4字节(32位),因此最大值是15,这意味着TCP首部最大长度可以是60字节。该字段用于定位数据部分的起始位置。

若首部长度为20字节,那么其4位字段应该是 0101。

保留字段 (Reserved):6位,预留供将来使用,目前必须设置为0。

控制标志 (Flags):6位,用于控制TCP连接的状态,可以多个位一起设置。提供服务的服务端在通信过程中会收到各种各样的报文或者是来自不同客户端的报文。因为TCP报文需要区分类型。不同报文的要做的工作不同(建立连接的报文、正常通信的报文、断开连接的报文)。这些标志位包括:

标志位名称 英文简称 功能描述
紧急指针有效标志 (Urgent Pointer) URG 当URG位为1时,表示紧急指针字段有效,指出紧急数据的结束位置。用于传输需要优先处理的数据。
确认号有效标志 (Acknowledgment Number) ACK 当ACK位为1时,表示确认号字段有效。接收方通过该标志确认已收到的数据。
推送标志 (Push) PSH 当PSH位为1时,表示接收方应立即将数据推送到应用层,而不是缓冲。用于需要即时处理的数据传输。
重置标志 (Reset) RST 当RST位为1时,表示重置连接。通常用于异常终止连接或拒绝非法连接请求。
同步标志 (SYNchronize) SYN 当SYN位为1时,表示同步序列号,用于建立连接(三次握手的第一步)。
终止标志 (Finish) FIN 当FIN位为1时,表示发送方已经完成数据发送,请求释放连接。

窗口大小 (Window Size):16位,表示接收方可以接收的字节数,用于流量控制。

校验和 (Checksum):16位,TCP使用该字段来检查报头和数据是否在传输过程中出现错误。发送方计算校验和并附加在报头中,接收方进行验证。

紧急指针 (Urgent Pointer):16位,仅在URG标志有效时使用,指出紧急数据的结束位置(或者是是紧急数据的字节的顺序编号),通常在需要高优先级传输的数据时使用。

选项 (Options ):可变长度,用于支持各种扩展功能。经常使用的为最大报文段大小(MSS,Maximum Segment Size),TCP 连接通常在第一个通信的报文中指明这个选项,它指明当前主机所能接受的最大报文的长度。

填充 (Padding):为了使TCP报头长度是32位的倍数,报头后可能会附加一些填充值。

下面 Linux 中 TCP 头部结构的定义,用于操作系统中的TCP协议处理。它展示了TCP报头各字段在大端序和小端序环境下的定义。

根据TCP首部字段理解报头和有效载荷是如何分离的

在TCP报文段中,数据偏移量(也称为4位首部长度)是一个4位字段,它表示TCP首部的长度。数据偏移量的单位是4字节(32位),所以它的值范围是5到15(因为TCP首部的最小长度是20字节,即5个4字节单元,最大长度是60字节,即15个4字节单元)。

数据偏移量的作用

  1. 指示TCP首部的结束位置: 数据偏移量字段告诉我们TCP首部的长度。通过这个长度,我们可以确定TCP首部的结束位置,从而知道从哪里开始是数据部分(有效载荷)。
  2. 分离首部和数据: 数据偏移量实际上是TCP报文段中数据部分的起始位置。这个位置是相对于报文段的起始位置计算的。通过数据偏移量字段,接收端可以正确地从报文段中提取出TCP首部和数据部分。

假设数据偏移量字段的值是x,那么TCP首部的实际长度就是 x * 4 字节。接收方在解析TCP报文段时,通过读取数据偏移量字段,就能确定TCP首部的长度,并从这个长度之后的位置开始读取数据部分。

例如:

  • 如果数据偏移量是5,那么TCP首部长度是 5 * 4 = 20 字节,这也是没有任何选项的标准TCP首部长度。

  • 如果数据偏移量是8,那么TCP首部长度是 8 * 4 = 32 字节,意味着首部包含了选项字段。

分离过程

  1. 读取TCP首部 : 从报文段的开始位置读取x * 4字节的数据,这就是TCP首部的内容。
  2. 提取有效载荷 : 在TCP首部之后的数据即为有效载荷。从报文段的x * 4字节位置开始读取剩余的数据,就是TCP的数据部分。

为什么 UDP 头部没有首部长度字段,而 TCP 有呢?

TCP 有可变长的选项字段,而 UDP 首部长度是不会变化的,无需多一个字节去记录 UDP 的首部长度。

2、TCP 的三次握手

TCP的三次握手用于建立一个连接。通常会发送如下场景:

1️⃣服务端必须准备好接收外来的连接。这通常通过调用socket、bind和listen这三个函数来完成,我们称之为被动打开(passive open)。

2️⃣客户端通过调用connect发起主动打开(active open )。这导致客户 TCP 发送一个 SYN(同步)报文,它告诉服务端客户将在(待建立的)连接中发送的数据的初始序列号。通常 SYN 报文不携带数据,其所在 IP 数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。

3️⃣服务端必须确认(ACK)客户的SYN,同时自己也得发送一个SYN分解,它含有服务端将在该同一连接中发送的数据的初始序列号。服务端在单个报文中发送SYN和对客户SYN的ACK(确认)。

4️⃣客户必须确认服务端的SYN。

如下图所示:

上图给出的客户端序列号为 J ,服务端的初始序列号为K。ACK中的确认号是发送这个ACK的一端所期待的下一个序列号。因为SYN占据一个字节的序列号空间,所以每一个SYN的ACK中的确定号我就是该SYN的序列号+1。类似的,每一个FIN的ACK中的确认号为该FIN的序列号+1。

建立TCP连接涉及的一系列步骤,就像使用电话系统一样。首先,socket函数的作用就像是确保你有一部电话可以使用。接着,bind函数的作用是公布你的电话号码,这样其他人就能联系到你。然后,listen函数相当于开启电话的铃声,这样当有人打电话给你时,你就能知道。connect函数则是你根据对方的电话号码拨打电话。最后,accept函数发生在你接听电话的那一刻。accept函数返回的客户端标识,类似于电话的来电显示功能,它告诉你谁在给你打电话。不过,与电话系统不同的是,accept函数是在你接听电话后才能告诉你来电者的信息,而在电话系统中,你可以在接听电话之前就看到来电者的号码。

也就是说,只有处于listen状态下的服务端,才能接收SYN报文。accept函数是在三次握手完成后,accept才能获得链接(即accept不参与三次握手)。客户端connect函数发起了三次握手。

3、TCP 的四次挥手

TCP建立一个连接需要3个报文,终止一个连接需要4个报文。

1️⃣某一个应用进程首先调用close,我们称该端执行主动关闭(active close)。该端的TCP是发送一个FIN报文,表示数据发送完毕。

2️⃣接收到这个FIN的对端执行被动关闭(passive close )。这个FIN由TCP确认。它的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程(放在已排队等待该应用进程接收任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应的连接上再无额外数据可以接收。

3️⃣一段时间后,接收到这个文件结束符的应用进程将吊close关闭它的套接字。这导致它的TCP也发送一个FIN。

4️⃣接收这个FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。

由于每个分组都需要一个FIN和ACK,因此通常需要四个报文。

为什么是通常?

因为某些情况下,第一步的FIN会随着数据一起发送;另外,第二步和第三步发送的报文都出自执行被动关闭那一端,有可能被合并成一个报文一起发送。

如下图所示:(下图虽是客户端主动关闭,但任何一方都可以主动关闭)

类似SYN,一个FIN也占据1个字节的序列号空间。因此,每个FIN的ACK确认号就是这个FIN的序列号+1。

在第二步和第三步之间,从执行被动关闭一端到执行主动关闭的一端流动数据是可能的。我们称之为半关闭 (shutdown函数)。

当套接字被关闭时,其所在端的TCP各自发送了一个FIN。我们在图中指出,这是由应用进程调用close而发生的。不过应当一个Linux进程无论是否是资源终止时,所有打开的文件描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。

4、TCP 的全双工通信

TCP 允许通信的双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓冲区和接收缓冲区,用来临时存放双向通信的数据。在发送时,应用程序把数据发送给 TCP 的缓存后,就可以做自己的事,而 TCP 在合适的时候把数据发送出去。在接受时,TCP 把收到的缓存数据放入缓存,上层的应用程序在合适的时候读取缓存中的数据。

每一个 TCP 套接字有一个发送缓冲区,我们可以使用SO_SNDBUF选项来更改缓冲区的大小。当某个进程调用write时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区已经满了),该应用进程将被投入睡眠。这里假设该套接字是阻塞的,它是通常的默认设置。内核将不从write系统调用返回,直到应用进程缓冲区中所有的数据都复制到套接字发送缓冲区。因此,从写一个TCP套接字的write调用成功仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的 TCP 或应用进程已经收到数据。

这一端的 TCP 提取套接字发送缓冲区中的数据并把它发送给对端TCP ,其过程基于 TCP 的所有规则。对端 TCP 必须确认已经收到数据,只有来自对端的ACK的到达,本端 TCP 才能从套接字的发送缓冲区丢失已经对端已经确认收到的数据。

也就是说,TCP必须为已经发送的数据保留一个副本,直到确认它被对端收到。

那么TCP是如何与IP层协作的呢?

  1. TCP数据传输 :
    • TCP将应用层的数据分割成一定大小的块,MSS是在TCP连接建立过程中,通过TCP选项由对端通告的值。如果对端没有发送MSS选项,则默认的MSS大小为536字节。这个值是基于IPv4的最小重组缓冲区大小(576字节)减去IPv4首部(20字节)和TCP首部(20字节)的大小。
  2. TCP分节的构成 :
    • 每个数据块会被附加一个 TCP 首部,从而形成一个 TCP 分节。TCP 首部包含了用于数据传输控制的信息,如序列号、确认号、窗口大小等。
  3. IP层处理 :
    • IP层接收来自TCP层的分节,并为每个分节附加一个IP首部,形成IP数据报。IP首部包含了目的IP地址和源IP地址等信息。
    • IP层根据数据报的目的IP地址查找路由表,确定数据报应该通过哪个外出接口发送。
    • 在发送数据报之前,IP 层可能会根据数据链路的MTU(最大传输单元)对数据报进行分片。然而,通过使用MSS选项和路径MTU发现功能,TCP 试图避免 IP 层的分片,因为分片会增加网络的开销和复杂性。
  4. 数据链路层的输出队列 :
    • 每个网络接口都有一个输出队列,用于暂存即将发送到网络的数据报。
    • 如果输出队列已满,新到达的数据报将会被丢弃,并且错误信息会沿着协议栈向上传递,从数据链路层到 IP 层,再从 IP 层到 TCP 层。
  5. 错误处理 :
    • 当 TCP 层接收到错误信息时,它会意识到某个分节没有被成功发送。TCP 将负责在以后的某个时刻重传这个分节。
    • 对于应用进程来说,这种分节重传的过程是透明的。应用进程不会直接感知到这种暂时性的网络问题,因为 TCP 协议会自动处理这些情况,确保数据的可靠传输。

TCP 可以将数据可靠地从一台主机传输到另一台主机,并且在网络出现问题时进行适当的错误处理和重传。

TCP 全双工通信的实现与用户端和服务端各自拥有独立的发送缓冲区和接收缓冲区密切相关。这些缓冲区的存在是实现全双工通信的重要基础。

全双工通信 (Full_Duplex Communication)是指通信的双方可以同时进行数据发送和接收。这意味着,用户端和服务端可以在同一时刻互相发送和接收数据,而不会发生冲突。

在 TCP 中,每个连接的双方(客户端和服务端)都有两个缓冲区:

  • 发送缓冲区(Send Buffer):用于存储待发送的数据。
  • 接收缓冲区(Receive Buffer):用于存储已接收到但尚未被应用程序处理的数据。

这些缓冲区的存在使得数据在发送和接收过程中能够被暂时存储,从而实现流量控制和数据处理的解耦。

由于 TCP 连接的双方都有独立的发送缓冲区和接收缓冲区,每一方都可以在以下情况下独立操作:

  • 发送数据:当用户端想要发送数据时,会将数据写入发送缓冲区,TCP 协议栈会从该缓冲区中读取数据并通过网络发送给服务端。同样地,服务端也可以将数据写入其发送缓冲区,通过网络发送给用户端。
  • 接收数据:当数据到达接收端时,数据会被放入接收缓冲区,等待应用程序进行处理。用户端和服务端的接收缓冲区独立存在,彼此之间互不影响。

TCP 实现全双工通信的原因在于每一端(客户端和服务端端)都拥有独立的发送缓冲区和接收缓冲区,这使得双方可以在同一时刻独立地发送和接收数据。通过滑动窗口、确认机制和流量控制等机制,TCP 确保了数据传输的可靠性和有序性,同时实现了高效的全双工通信。

TCP通过一个套接字描述符,可以既读又写

TCP 套接字是全双工的通信通道,允许数据在两个方向上同时传输。

TCP 本质上是一种面向连接的、可靠的、全双工的数据传输协议。全双工意味着数据可以同时在两个方向上传输,即:

  • 读操作:从套接字中接收数据。
  • 写操作:向套接字中发送数据。

在Linux操作系统中,文件描述符是一个抽象的指针,用于标识内核为进程打开的文件、设备、管道或套接字。每个打开的文件或套接字都分配一个唯一的文件描述符。

当创建一个 TCP 套接字时,操作系统分配一个文件描述符给这个套接字。这时,套接字作为文件描述符的一种特殊形式,具备以下特点:

  • 读写操作支持 :同一个文件描述符可以被用来调用 read(或 recv)和 write(或 send)函数。读取操作从套接字接收数据,写入操作通过套接字发送数据。
  • 面向连接:在建立连接后,套接字两端都可以进行读和写操作,不需要再区分是读套接字还是写套接字。

tcp中读到的报文可能读到一个半或半个等,称为tcp粘包问题。

TCP粘包问题是指由于TCP本身是面向流的协议,所以在传输数据时,没有边界,这就导致应用层在读取TCP数据时可能会读取到一个完整的消息,也可能读取到多个消息的部分,或者一个消息的部分,这就是所谓的粘包问题。

向网络里写的是添加了报头的数据,如果本地把数据写到文本文件里,写到文件里,解析文件时,就边解析边处理。


三、TCP 的状态转换

下图是 TCP 涉及连接建立和连接终止的状态转换图。

1、TCP 连接的建立(三次握手)状态

TCP连接(三次握手) :

客户端:CLOSED → SYN_SENT → ESTABLISHED

服务端:CLOSED → SYN_RECEIVED → ESTABLISHED

CLOSED:初始状态,表示没有任何连接或活动。当客户端或服务端尚未开始通信时,TCP套接字处于这个状态。

SYN_SENT :当客户端主动发起连接请求时(发送 SYN 包),它进入此状态。此时,客户端已经发送了 SYN 报文并等待服务端的响应。

SYN_RCVD :服务端收到客户端的 SYN 请求后,它回复一个 SYN_ACK 报文并进入此状态。它表示服务端正在等待客户端的确认。

ESTABLISHED :客户端收到服务端的 SYN_ACK 并发送 ACK 报文后,服务端进入 ESTABLISHED 状态,客户端同时也进入此状态。这是一个表示连接已成功建立的状态,双方可以开始数据传输。

  1. 应用进程执行主动打开 :
    • 当应用进程想要建立一个 TCP 连接时,它会指示 TCP 执行主动打开操作。
    • 在CLOSED状态下,TCP 为即将建立的连接分配资源,并发送一个SYN(同步)报文到目标服务端。
    • 发送SYN之后,TCP 的状态从CLOSED变为SYN_SENT。
  2. 接收带ACK的SYN :
    • 目标服务端在收到客户端发送的SYN后,会发送一个SYN_ACK报文作为响应。
    • SYN_ACK报文中既包含SYN也包含ACK,SYN用于初始化服务端端的序列号,ACK用于确认客户端的SYN。
    • 当客户端 TCP 接收到这个SYN_ACK报文时,它确认了服务端已经收到了它的SYN,并且服务端也准备好建立连接。
  3. 发送ACK并进入ESTABLISHED状态 :
    • 为了完成三次握手,客户端需要发送一个ACK报文来确认服务端的SYN。
    • 在发送ACK报文之后,客户端TCP的状态从SYN_SENT变为ESTABLISHED。
    • 此时,服务端也接收到客户端的ACK,确认客户端已经准备好进行数据传输,服务端端的状态也从SYN_RCVD变为ESTABLISHED。

从上面的过程我们可以发现,前两次握手是不可以携带数据的,而第三次握手是可以携带数据的。因为主动连接的一方已经收到了ACK,认为自己已经成功建立了连接,可以开始发送数据。

但客户端认为连接建立好的时间与服务端认为连接建立好的时间有差别。

客户端和服务端认为连接建立好的时间存在差异,是由于TCP三次握手的顺序和状态转换所导致的。

客户端视角 :客户端发送SYN后进入SYN_SENT状态。当客户端收到来自服务端的SYN_ACK响应时,它发送ACK作为确认,并且立即将状态从SYN_SENT变为ESTABLISHED。在客户端的角度,连接在发送完ACK之后就被认为是建立好的,因为它已经完成了它需要做的所有握手步骤。

服务端视角 :服务端在收到客户端的SYN后,发送SYN_ACK并进入SYN_RCVD状态。服务端需要等待客户端的ACK响应,一旦收到ACK,服务端才将状态从SYN_RCVD变为ESTABLISHED。在服务端的角度,连接在收到客户端的ACK之后才被认为是建立好的。

因此,客户端和服务端认为连接建立好的时间点不同:

  • 客户端认为连接建立好的时间是它发送ACK之后。
  • 服务端认为连接建立好的时间是它收到ACK之后。

从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

这种时间上的差异是由于网络延迟造成的。ACK报文从客户端到服务端可能需要一些时间,这取决于网络条件。尽管存在这种时间差异,但它通常很小,不会对实际的数据传输造成影响,因为 TCP 协议设计时已经考虑到了网络延迟和网络的不确定性。

三次握手的目的是让通信的双方都确认连接已经建立,但这个过程并不保证100%成功。

第一次握手 :服务端接收客户端发来的SYN。如果服务端接收到了,服务端就可以认为:客户端的发送能力,和服务端的接收能力是正常。

第二次握手 :服务端发送SYN_ACK给客户端。如果客户端接收到了,客户端此时就可以认为:客户端的收发能力是正常的,服务端的收发能力也是正常的。但是服务端此时仍需要确认客户端的接收能力是否正常。

第三次握手 :服务端接收客户端发来的ACK。此时服务端就可以认为:客户端的收发能力是正常。

  • 如果客户端没有收到服务端的SYN_ACK,它会重试发送SYN,直到达到一定的重试次数,然后放弃。
  • 如果服务端没有收到客户端的ACK,它会等待一段时间后重新发送SYN_ACK,直到达到一定的重传次数,然后放弃。

半连接队列和全连接队列

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列 :用于存储尚未完成TCP三次握手的连接请求。具体来说,它包含以下状态的连接SYN_RCVD。服务器已经收到了客户端的SYN请求,并且已经回复了SYN_ACK,但还没有收到客户端的ACK确认。

当服务器收到一个SYN请求时,它会创建一个半开连接,并将该连接添加到半连接队列中。服务器会等待客户端的ACK报文来完成握手过程。如果服务器没有在预定的超时时间内收到ACK,它将删除该连接并释放资源。

  • 全连接队列 :用于存储已经完成TCP三次握手但尚未被应用程序的accept调用取走的连接。具体来说,它包含以下状态的连接ESTABLISHED。客户端和服务器已经完成了三次握手,连接已经建立,但服务器应用程序尚未调用accept函数来处理这个连接。

当服务器收到客户端的ACK报文并完成握手后,连接将从半连接队列移动到全连接队列。在全连接队列中,连接等待被应用程序处理。如果全连接队列满了,新的完成握手的连接可能会被丢弃,导致客户端连接失败。

面试题:为什么建立连接是三次握手

一次握手 :如果仅有一次握手,比如只发送 SYN 报文,那么服务端无法得知客户端是否已经收到它的响应(SYN_ACK),也无法确定客户端是否准备好进行数据传输。这可能导致服务端资源的浪费,因为服务端会为未确认的连接保留资源。容易受到SYN洪水攻击。

SYN洪水攻击:攻击者向目标服务器发送大量的SYN请求,但并不发送后续的ACK报文来完成握手过程。

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的 。服务器为每个收到的SYN请求分配资源(如内存中的连接记录),并等待客户端的ACK报文。由于攻击者不发送ACK,服务器会保留这些资源一段时间,等待可能永远不会到达的ACK报文。如果攻击者持续发送大量的SYN请求,服务器资源(如半开连接队列)将被迅速耗尽。

SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。

两次握手 :如果是两次握手(例如客户端发送 SYN,服务端发送 SYN_ACK),客户端无法确认服务端是否已准备好接收数据。两次握手缺乏对通信双方的确认,可能导致一些问题:

  • 旧的重复连接:在网络中,可能存在一些延迟的、失效的报文。如果只进行两次握手,一个旧的 SYN 报文可能会导致服务端错误地建立连接,从而导致数据的不一致。
  • 半开连接和资源浪费:如果客户端因网络问题没有收到服务端的 SYN_ACK报文,那么服务端会一直等待客户端的确认,而不会释放资源,可能会导致资源浪费和服务不可用。

三次握手时客户端需要先于服务端建立好连接,服务端在第三次握手之前不会为连接分配大量资源,避免了资源浪费。因此,需要三次握手才能确认双方的接收与发送能力是否正常。

理由一:保证信道(网络)是健康的。

双方都能发送和接收:在三次握手过程中,客户端和服务器都会发送和接收报文。这确保了双方都能正常地通过网络进行通信。

确认全双工:通过三次握手,双方都确认了对方能够发送和接收数据,这为全双工通信打下了基础。

理由二:确保双方操作系统(TCP)是健康的且愿意通信的。

三次握手确保了双方操作系统(TCP)的健康性和通信意愿。

资源分配:在三次握手的过程中,双方都会分配资源(如TCP控制块)来管理这个连接。这确保了双方都有资源来处理这个连接。

状态确认:通过三次握手,双方都确认了对方的状态(如客户端发送SYN,服务器发送SYN_ACK,客户端发送ACK)。这确保了双方都处于准备通信的状态。

理由三:阻止重复历史连接的初始化。 防止旧的重复连接初始化造成混乱。

历史连接指的是那些已经完成但尚未从网络中完全消失的旧连接。如果一个新的连接请求与一个旧连接的序列号相同,那么可能会发生以下情况:旧的数据包可能因为网络延迟而晚到达,如果这些数据包被错误地认为是属于新连接的,那么它们可能会干扰新连接的数据传输。

假设客户端尝试与服务端建立TCP连接,但由于网络问题,可能会发生以下情况:

  1. 客户端发送初始SYN报文 :客户端首先发送一个SYN报文(序列号为90)来请求建立连接,但这个报文在网络中阻塞了。
  2. 客户端宕机并重启 :在发送SYN报文后,客户端宕机。当客户端重启后,它忘记了之前的连接尝试,并再次发送一个新的SYN报文(序列号为100)来建立连接。
  3. 网络拥堵导致旧SYN报文晚到达 :由于网络拥堵,原先的SYN报文(序列号为90)在客户端发送新的SYN报文之后才到达服务端。

客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:

  1. 旧SYN报文到达服务端 :旧SYN报文(序列号为90)先于新的SYN报文到达服务端。服务端响应这个旧SYN报文,发送一个SYN_ACK报文,确认号为91(90+1)。
  2. 客户端识别并拒绝旧连接 :客户端收到服务端的SYN_ACK报文,但期望的确认号是101(100+1),而不是91。因此,客户端识别出这是一个不属于当前连接尝试的报文,并发送一个RST(重置)报文给服务端。
  3. 服务端释放连接 :服务端收到RST报文后,会释放这个半开连接,不再等待客户端的ACK。
  4. 新SYN报文到达服务端 :随后,新的SYN报文(序列号为100)到达服务端。这次,客户端和服务端可以正常完成三次握手过程,建立新的连接。

通过这个过程,TCP的三次握手机制有效地防止了历史连接的初始化,确保了新连接的建立不会受到旧连接数据包的干扰。这是TCP设计中的一个关键特性,保证了连接的可靠性和数据传输的安全性。

RST(reset):通常用于重置连接。为了解决建立链接出现异常的问题。连接重置就是让双方重新进行三次握手。

注:每次建立 TCP 连接时,初始化的序列号都要求不一样。

如果客户端和服务端在每次建立连接时都使用相同的初始化序列号,那么就无法区分新连接和历史连接的数据包。

  1. 数据包混淆:如果两个连接使用相同的序列号,接收方将无法区分哪个数据包属于哪个连接。这会导致数据包混淆,使得接收方无法正确地按照顺序处理数据包。
  2. 历史连接干扰:历史连接的数据包可能会被错误地认为是新连接的数据包,从而干扰新连接的数据传输。这可能导致数据包的乱序、重复或丢失。
  3. 安全性问题:如果攻击者能够预测新连接的序列号,他们可能会尝试利用这个信息来干扰或窃取新连接的数据。这可能会导致数据泄露或服务中断。

为了防止这些问题,TCP协议设计了一个随机化初始序列号的机制。每个TCP实现都会选择一个随机的初始序列号,以确保每次连接的序列号都是唯一的。

若三次握手最后一次ACK丢失 会发生什么?

三次握手最后一次ACK,即客户端发送给服务端的确认收到服务端SYN_ACK的ACK。

客户端收到服务端的 SYN_ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISHED状态。如果第三次握手丢失,服务端就认为并没有建立好连接。触发超时重传机制,重发SYN_ACK报文,直到第三次握手成功,或者达到最大重传次数。达到最大重传次数后,服务器最终会放弃连接。

若第三次握手丢失了,但客户端认为连接已经成功建立,向服务端发送数据?

服务端在收到数据包时,发现该包不属于任何已建立的连接,因为服务端还未收到第三次握手的 ACK 报文。 发送 RST 。服务端会认为收到的包是一个错误的包(即在未建立的连接上发送的数据),因此根据 TCP 协议,会发送一个带有 RST 标志的报文来重置连接。这个RST报文会告诉发送方(客户端),当前尝试的连接是不存在的,或者不应该发送数据。

资源释放:发送 RST 报文会立即释放与连接相关的资源,但可能会丢失未传输的部分数据。

对等方的处理:接收到 RST 报文的一方会立即关闭连接,不会尝试重新传输未确认的数据。

2、TCP 连接的终止(四次挥手)状态

TCP连接终止(四次挥手) :

主动关闭方:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

被动关闭方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

FIN_WAIT_1 :当一方(通常是客户端)主动关闭连接时,它发送 FIN 报文并进入此状态。这表示它已经发出了连接终止请求,但还没有收到对方的确认。

FIN_WAIT_2 :当对方收到 FIN 报文并发送了 ACK 进行确认,发起关闭的一方进入此状态。此时,它在等待对方发送自己的 FIN 报文。

TIME_WAIT :当主动关闭的一方收到对方的 FIN 并发送 ACK 进行确认后,进入 TIME_WAIT 状态。该状态持续一段时间(通常是2倍的最大报文段寿命2MSL,约几秒钟),确保对方已经收到最后的 ACK 并防止旧的报文在网络中被重新传输。

CLOSE_WAIT :被动关闭的一方在收到对方的 FIN 后,进入 CLOSE_WAIT 状态,表示连接正在关闭,但该方可能还需要处理一些剩余的事务(如发送剩余数据)。

LAST_ACK :被动关闭的一方在发送 FIN 后,等待对方的最后确认(ACK),进入 LAST_ACK 状态。一旦收到确认,连接终止。

CLOSE :最终状态。双方都完成了 FIN 和 ACK 的交换,连接彻底关闭,TCP进入初始的 CLOSE 状态。

  1. 客户端发送FIN报文 :客户端首先发送一个FIN报文,表示它没有更多的数据要发送。发送FIN的客户端进入FIN_WAIT_1状态,等待服务端的确认。
  2. 服务端发送ACK报文 :服务端收到FIN报文后,发送一个ACK报文作为确认。服务端进入CLOSE_WAIT状态,表示它已经准备关闭本地应用程序的连接,但还在等待应用程序的通知。
  3. 客户端收到ACK报文 :客户端收到服务端的ACK报文后,进入FIN_WAIT_2状态。
  4. 服务端发送FIN报文 :服务端在应用程序准备好关闭连接后,发送一个FIN报文给客户端。服务端进入LAST_ACK状态,等待客户端的确认。
  5. 客户端发送ACK报文 :客户端收到服务端的FIN报文后,发送一个ACK报文作为最后的确认。客户端进入TIME_WAIT状态,等待2MSL以确保服务端收到ACK报文。
  6. 服务端收到ACK报文 :服务端收到客户端的ACK报文后,进入CLOSE状态,表示连接已经完全关闭。
  7. 客户端进入CLOSE状态 :客户端在经过2MSL一段时间后,自动进入CLOSE状态,表示客户端也完成连接的关闭。

每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。 只有主动关闭连接的,才有 TIME_WAIT 状态。

如果第一次挥手丢失 :如果客户端发送的FIN报文丢失,客户端会等待一段时间(通常由TCP的超时重传机制决定),如果没有收到服务端的确认(ACK报文),客户端会重传FIN报文。

如果第二次挥手丢失 :当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。但ACK不会重传。客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

如果第三次挥手丢失 :当服务端(被动关闭方)收到客户端(主动关闭方)的FIN报文后,内核会自动回复一个ACK报文,并且将连接状态设置为CLOSE_WAIT。这个状态的含义是,服务端已经准备好关闭本地应用程序的连接,但是它还在等待应用程序的通知来完成实际的关闭操作。即等待进程调用close函数来触发服务端发送FIN报文。当服务端处于CLOSE_WAIT状态时,如果应用程序调用了close函数,内核会自动发送一个FIN报文,同时连接状态会更新为LAST_ACK。在这个状态下,服务端等待客户端返回ACK报文以确认连接关闭。如果服务端迟迟收不到客户端的ACK报文,服务端会重发FIN报文,以确保客户端能够接收到这个关闭请求(超时重传机制)。

如果第四次挥手丢失 :服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文。如果服务端重传的FIN到达客户端,客户端会认为这是一个新的FIN请求,并发送ACK确认。

在特定情况下,四次挥手是可以变成三次挥手的。

详解TIME_WAIT 状态

在断开连接的状态图中,当一端执行主动关闭操作时,它将进入一个称为TIME_WAIT的状态。这个状态的持续时间被设定为2倍的最大报文生存期(2MSL,Maximum Segment Lifetime)。

TIME_WAIT状态确保在一个TCP连接关闭后,一段时间内不会立即重新使用相同的四元组(源IP、源端口、目标IP、目标端口)进行新的连接。这段时间通常是两个最大段生存时间(2MSL),即数据包在网络中生存的最长时间。这有助于确保所有旧连接的延迟数据包在网络中完全消失,不会误与新连接混淆。

为什么需要TIME_WAIT状态?

理由一:可靠地实现TCP全双工连接的终止。

  • TCP连接是全双工的,意味着数据可以在两个方向上独立传输。当需要终止这样的连接(即关闭全双工)时,每个方向的数据流都需要被独立地关闭,即它必须正确处连接中止过程4个报文任何一个报文丢失的情况。
  • 也就是说,必须确保FIN报文被对端正确接收。如果最后一个ACK在传输过程中丢失,那么发送FIN报文的一端会认为没有收到确认,并会重新发送FIN报文。为了能够正确地响应这个重发的FIN报文,执行主动关闭的端点(即发送最后一个ACK的端点)必须维护状态信息(即维护TIME_WAIT状态信息),以便能够重新发送ACK报文。如果它不维护这些信息,它可能会响应一个RST报文,这会被对端解释为一个错误。因此,执行主动关闭的端点需要进入TIME_WAIT状态,以保持足够的状态信息来处理这种可能性。客户端在收到服务端重传的 FIN 报文时,TIME_WAIT 状态的等待时间,会重置回 2MSL。
  • 本例子也说明了为什么执行主动关闭的那一端是处于TIME_WAIT状态的那一端 :由于主动关闭连接的一端需要负责确保所有报文都被正确处理,因此它需要进入TIME_WAIT状态。

理由二:允许在网络中残留的旧报文过期,防止它们对新连接产生影响。

  • TIME_WAIT状态允许网络中可能存在的旧报文过期,从而避免这些旧报文干扰随后建立的新连接。这样可以确保新连接的数据不会受到之前连接残留数据的影响,保证了网络通信的稳定性。
  • 当一个TCP连接关闭后,如果在相同的IP地址和端口之间很快又建立了新的连接,这个新连接被称为前一个连接的"化身"。为了避免来自旧连接的重复分组(可能在网络中长时间徘徊)被新连接错误地解释,TCP需要确保这些旧的分组在网络中已经消逝。TIME_WAIT状态通过保持2MSL的时间长度来实现这一点。MSL是最大报文生存期,即一个TCP报文在网络中可能存在的最长时间。通过保持TIME_WAIT状态2MSL的时间,可以确保在这个时间内,任何在网络中徘徊的旧分组都会被丢弃,从而不会干扰到新的连接。这保证了当一个新连接被建立时,它不会受到来自之前连接的老分组的干扰。

这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

如果TIME_WAIT状态过多会怎么样?

如果系统中的TIME_WAIT状态过多,可能会出现以下几种情况:

  1. 消耗系统资源 :每个处于TIME_WAIT状态的连接都会占用系统的一些资源,包括内存和文件描述符。如果TIME_WAIT状态过多,可能会导致资源耗尽,影响新连接的建立。

  2. 端口耗尽 :由于TIME_WAIT状态会占用本地端口一段时间,如果短时间内大量连接进入TIME_WAIT状态,可能会耗尽可用的端口资源,导致新的连接无法建立。

    • 如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,占满了所有端口资源,那么就无法对目的 IP+ 目的 PORT都一样的服务端发起连接了,但是被使用的端口,还是可以继续对另外一个服务端发起连接的。这是因为每个外向连接都需要一个唯一的源端口来区分不同的连接。

      也就说,对于已经处于TIME_WAIT状态的端口,如果目的服务端的目的 IP+ 目的 PORT组合不同,客户端仍然可以使用这些端口来建立新的连接。这是因为TCP连接是由四元组(源IP、源端口、目的IP、目的PORT)唯一标识的。只要这四元组中的任何一个元素不同,就可以认为是一个新的连接。

    • 服务端通常使用固定的端口来监听客户端的连接请求。如果(并发处理大量连接的)服务端主动关闭连接,很多连接进入TIME_WAIT状态,那么服务器也不能立即使用相同的四元组建立新连接。

如果客户端希望断开连接而服务端仍然有数据要发送,TCP允许通信双方有序地关闭连接,同时允许未完成的数据传输。但客户端已经关闭,服务端发送的数据如何被客户端得到呢?

当客户端已经关闭连接,服务端尝试发送的数据将无法被接收,并最终导致服务端意识到连接的终止。TCP 协议会通过 RST 报文或其他机制通知服务端,随后服务端会关闭连接并停止发送数据。所有未成功传输的数据都将丢失,因此应用层应具备处理这种情况的逻辑。

但是若客户端只想收信息,不发信息。套接字文件描述符是全双工的,我们可以使用shutdown()。shutdown()函数提供了比直接调用close()函数更细粒度的控制,允许在关闭连接的不同阶段处理特定情况。例如,可以关闭写入端来结束发送数据而不立即关闭读取端,以继续处理收到的数据。

我们进行一下实验,我们实现的是一个并发的 TCP-echo 服务端。

服务端关闭accpet得到的文件描述符,那么在客户端退出,服务端未退出时,我们可以发现客户端会处于time_wait:

由ESTABLISHED转变为TIME_WAIT。

若服务端主动断开连接,四次挥手并未完成。netstat ntap 服务端进行处理数据而启动的进程(accept后启动新进程或线程处理任务)处于TIME_WAIT

服务端主动断开连接,四次挥手并未完全完成,处于TIME_WAIT状态。因此,我们再次启动服务端时,8888端口仍被占用,bind失败。

可以使用setsockopt解决此问题。

如果服务端出现大量CLOSE_WAIT状态。

我们将一个并发的 TCP-echo 服务端的close注释掉。

再次连接:

如果被动关闭方没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。

3、TCP 异常情况

在TCP网络通信中,会存在多种异常情况,这些异常情况会导致连接的非正常终止。

当进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使进程退出了,还是能与对端完成 TCP四次挥手的过程。

  1. 进程终止:当进程终止时,操作系统会释放该进程所持有的所有资源,包括文件描述符。对于TCP连接而言,这通常会导致以下行为:

    • 发送FIN :如果进程终止时,TCP 连接仍然打开,操作系统会自动发送一个FIN包来开始正常的四次挥手关闭过程。这与其他正常关闭连接的情况类似。
  2. 机器重启:机器重启的情况与进程终止类似,因为所有进程都会被终止,相应的文件描述符也会被释放:

    • 发送FIN :在重启过程中,操作系统会关闭所有打开的连接,并发送FIN包来关闭 TCP 连接。

当客户端 突然掉线/主机崩溃 时,TCP 协议栈无法立即感知连接的中断,因此会保持连接状态,直到保活探测机制触发。也就是下面的情况:

  1. 机器掉电/网线断开:当发生机器掉电或网线断开的情况时,连接的双方可能会以不同的方式响应:

    • 接收端 :接收端可能不会立即意识到连接已经断开。如果接收端尝试发送数据,它最终会收到一个由网络层返回的ICMP错误消息,表明目标主机不可达或者网络路径有问题。

    • TCP保活机制:TCP协议中有一个保活机制,用于检测连接是否仍然有效。保活机制会定期发送探测包来检查对方是否仍然在线。探测包的发送和确认应答可以帮助服务端检测到客户端的掉线情况。如果客户端收到保活探测包,会返回一个 ACK 包,表示仍然在线。

    • 接收端reset:如果接收端在尝试发送数据时发现连接已经断开,它会发送一个RST(Reset)包来快速关闭连接,而不是正常的四次挥手过程。

    • 应用层保活机制:某些应用层协议(如HTTP长连接、QQ等)实现了自己的保活或心跳机制,用于定期检测连接的对端是否仍然可用。如果检测到连接断开,应用层协议会尝试重新建立连接或者采取其他恢复措施。

TCP保活机制 (TCP keepalive)用于检测一个空闲的TCP连接是否仍然有效。

相关推荐
2301_777998342 小时前
ARP协议进阶:ARP缓存、RARP与ARP欺骗原理解析
linux·服务器·网络·网络协议·缓存
m0_715674437 小时前
可视化易用·低误报率·跨区域部署:知源-AI数据分类分级系统在金融行业的落地实践
网络·安全·网络安全
dk_tjy7 小时前
AI驱动的自动化攻击背景下:企业端点安全体系的技术架构与建设路径
网络·人工智能·安全·自动化·终端安全管理·数据防泄漏
ESDWAN9 小时前
海外系统偶尔打不开,怎么区分 DNS 异常与网络连接故障?
tcp/ip·https
昂好的day9 小时前
VisionMaster 通信模块实战(上):Modbus、TCP 收发、协议组装解析
开发语言·网络·网络协议·tcp/ip·c#
Escalating_xu11 小时前
【Linux 网络编程】connect 的断线重连:用状态机写一个能恢复连接的 TCP 客户端
linux·网络·tcp/ip
2401_8685347812 小时前
局域网络中信息安全方案设计及攻击防范技术(制造企业园区网)
服务器·网络
懂点Tech13 小时前
ctfshow MISC入门图片篇(信息附加):misc21解题思路
网络
CS_Zero14 小时前
计算机网络通信基础2:带宽和吞吐量、客户端和服务器、网络基础设施
网络·计算机网络
盛世宏博智慧档案14 小时前
算力机房多协议融合:Modbus TCP 温湿度变送器与 SNMP 设备混合组网方案
网络·网络协议·tcp/ip·传感器·温湿度