Network_TCP协议详解

[toc]

一、TCP/IP 四层网络模型与OSI七层网络模型简介

1.1 网络协议及网络模型

在计算机系统上,同一台设备上的进程间通信,有很多种方式,比如管道、消息队列、共享内存、信号等,而不同设备上的进程间通信,就需要网络通信,既然要通信,双方就要遵守一定的规则,这套规则就是网络协议。 OSI七层模型和TCP/IP协议的四层模型

OSI模型是国际标准化组织提出来的,是一个理论上的网络通信框架,它将网络通信过程分为7个层次,但是实际上,业界更常见的是TCP/IP模型,在实际应用中更简洁和实用。

TCP/IP模型更贴近现实的网络协议实现,TCP/IP模型由四个层次组成,每一层都有对应的协议和作用。

功能:

  • 在物理媒介上发送和接收数据帧(以太网、Wi-Fi、PPP 等)
  • 将 IP 数据报封装成适合传输介质的形式
  • 处理与物理网络硬件相关的细节(MAC 地址寻址、帧检验、错误处理)

1.2.1 集线器

两台电脑(电脑A和电脑B)之间可以使用网线连接进行通信,如果有电脑C、电脑D、电脑E、电脑F……,如果将这些电脑之间都使用网线建立起联系,会导致网线非常的多,并且非常的混乱。

于是乎,集线器出现了,每个电脑不需要再通过网线连接其他电脑,只需要连接集线器即可,电脑之间的数据包统一由集线器转发。 集线器的作用是将收到的数据包转发到所有出口(广播),不做任何处理,比如A想发送一个数据包到B,集线器就将这个数据包转发给电脑B、C、D、E、F,实际上,我们只是想发给电脑B而已,但是其他电脑也收到了原本想发给电脑B的数据包,那怎么办呢?

对于其他电脑而言,收到了不属于自己的消息,就丢掉,对于电脑B而言,收到了属于自己的消息,应当收下。

这种方式可以解决消息传递的问题,但付出的代价就是也很明显,有没有什么方法可以直接指定接收方后,集线器识别出接收方直接转发给接收方,而不采用广播方式发生给每一方。MAC地址就是一种解决方案。

1.2 MAC地址 MAC地址 是唯一的,每一个网络设备都有自己的MAC地址,是一个48bit的值,比如 00 : 0c : 29 : c4 : c0 : 0a,通常前3段表示厂商ID,后三段表示设备ID。

在发送数据包时,在数据包的头部添加上源和目的MAC地址,就像在寄快递的时候,需要填写发件人和收件人一样,比如电脑A的MAC地址为aa : aa : aa : aa : aa : aa,电脑B的MAC地址为bb : bb : bb : bb : bb : bb,不重复就行。当集线器将此数据包转发给出去后,B根据头部的MAC地址判断这个是发送给自己的,就收下,而C、D、E、F判断不是自己的,就丢弃。

在交换机内部,有一张MAC地址表,主要包含两个信息,一个是设备的MAC地址,另一个是该设备连接在交换机的哪个端口上。

如果电脑A发送数据包给电脑B,交换机发现数据包头部的目的MAC地址是B的,然后就查找MAC地址表,发现B的MAC地址表和端口2是对应的,于是就把数据包从端口2发给B。

1.3 交换机 有了集线器之后,每台电脑只需要连接集线器就好了,不需要与其他的电脑相互连接,但是每台电脑时不时地会收到垃圾消息,如果能将消息只发给目标MAC地址指向的那个电脑就好了,于是交换机出现了。 在交换机内部,有一张MAC地址表,主要包含两个信息,一个是设备的MAC地址,另一个是该设备连接在交换机的哪个端口上。

如果电脑A发送数据包给电脑B,交换机发现数据包头部的目的MAC地址是B的,然后就查找MAC地址表,发现B的MAC地址表和端口2是对应的,于是就把数据包从端口2发给B。

MAC 地址表的维护: 交换机内部的MAC地址,在最开始的时候是空的,是怎么逐步建立起来的呢?

  • 电脑A发送数据包给电脑B,数据包从端口1进入交换机,此时交换机可以在MAC地址表中记录下电脑A的MAC地址以及对应的端口1
  • 此时地址表中没有记录目标MAC地址,于是将数据包从所有端口发送出去,发给了所有电脑
  • 电脑B确认数据包是发给自己,于是做出响应,响应数据从端口2进入交换机,此时交换机记录下了电脑B的MAC地址以及对应的端口2
  • 就像上面这样不断地互相通信,交换机最终将MAC地址表完善起来

1.3 网络层(Internet Layer)

核心协议:互联网协议 (IP)

功能:

  • 将数据包从源主机路由到目标主机(跨越多个中间网络)
  • 确保数据包的路由选择,处理数据包在网络之间的移动和交付
  • 包括 IP 协议、ICMP 协议(用于诊断网络问题)、ARP 协议(用于将 IP 地址转换为物理地址)等

Tips:

  • 电脑发送数据包时必须知道源IP和目的IP
  • 若不在同一个子网,就通过ARP表得到默认路由的MAC地址,将数据包发送出去
  • 如果在同一个子网,就通过ARP得到目的MAC地址,发送出去
  • 路由器收到的数据包中的目标MAC地址必须是自己的
  • 路由器收到的数据包必须有目标IP地址
  • 通过查找路由表确定转发操作
  • 通过ARP表确定目标MAC地址

1.3.1 路由器 后来电脑越来越多,交换机的端口也不够用了,这个时候路由器出现了。路由器的每个端口都有自己独立的MAC地址,并且可以把数据再做一次转发。

1.3.2 IP地址 如果电脑A想发送数据包给电脑C,需要通过路由器转发一次,而发送数据包给电脑B时,不需要路由器转发,那怎么确定哪些需要路由器转发,哪些不需要呢?

于是又发明了一种新的地址,即IP地址。IP地址是一个32位的编号,分成了四段(如 192.168.100.1),每段是8位。

现在网络中的每个设备都有了IP地址,如下:

此时发送的数据包中不仅有MAC地址,还有IP地址:

  • 如果源IP和目的IP在同一个子网,就直接通过交换机发送;
  • 如果源IP和目的IP不在一个子网,就需要通过路由器转发;

电脑A和电脑B就处于同一个子网,电脑C和电脑D处于同一个子网,其实就是看IP地址的前三段,如果一致就是在同一个子网中。

说的官方一点,需要用到子网掩码的概念,将源IP和目的IP分别和这个子网掩码进行按位与运算,得到的结果相同,就表示在同一个子网,不相同就不在同一个子网。

现在如果电脑A要发送数据包给电脑C,需要经过以下几个步骤:

  • 电脑A通过子网掩码计算出自己与C不在同一个子网内,于是决定将数据包发给路由器
  • 电脑A首先将数据包交给交换机1,注意这个时候的源MAC地址是电脑A的,而目的MAC地址是路由器端口1的

Tips: 注意这里,路由器和交换机是不同的,交换机只是将进来的数据包转发出去,它自己并不会成为发送方或接收方,而路由器的端口具有MAC地址,因此路由器可以成为以太网的发送方或接收方。路由器的每个端口都具有MAC地址和IP地址。

  • 交换机1再将数据交给路由器,这时交换机发现目标MAC地址是ab : ab : ab : ab : ab : ab,于是从端口3发出去。

    MAC地址 端口
    aa:aa:aa:aa:aa:aa 1
    bb:bb:bb:bb:bb:bb 2
    ab:ab:ab:ab:ab:ab 3
  • 路由器将数据包发给交换机2,注意此时源MAC地址是路由器端口2的,目的MAC地址是电脑C的(注意只有MAC地址在变,IP地址其实是不变的。)

  • 最后交换机2将数据包发给电脑C

1.3.3 路由表

当数据包到达路由器之后,路由器和交换机的思路是一样的,都是通过查表判断转发目标,交换机判断的是MAC地址,而路由器判断的是IP地址。

从上面电脑A向电脑C发送数据包的过程知道,路由器接收到数据包之后,就丢弃了包开头的MAC地址,MAC地址的作用就是将数据包送达路由器,其中接收方的MAC地址就是路由器端口的MAC地址,因此当数据包到达路由器后,数据包中的MAC地址的任务已经完成,就可以丢弃。

Tips: 通过路由器转发的数据包,接收方MAC地址为路由器端口的MAC地址。

接下来,路由器会根据IP地址判断数据包的转发操作,这就需要用到路由表,这个表需要将目的IP和自己的端口对应起来,路由器收到数据包之后,就在路由表中查询目的IP,以此判断转发目标,如下:

目的地址 子网掩码 端口
192.168.0.0 255.255.255.0 0
192.168.0.254 255.255.255.255 0
192.168.1.0 255.255.255.0 1
192.168.1.254 255.255.255.255 1

即目的IP地址为192.168.0.xxx,都转发到0号端口,192.168.1.xxx,都转发到1号端口.

注意路由表的第一行是默认路由,意味着所有未在表中明确列出目的地的数据包都通过这条路由发送。

当电脑尝试将数据包发给一个不在同一个子网的目的地时,通常会使用默认路由,比如在上面这个表中,网关192.138.1.1,通常就是路由器地址,电脑会首先通过ARP表,得到192.138.1.1对应的MAC地址,并添加MAC头部,然后发给这个路由器

1.3.4 ARP 当路由器转发数据包时,它是怎么知道电脑C的MAC地址的?

是通过ARP协议,在电脑和路由器里面,都有一张ARP缓存表,表中记录着IP和MAC地址的对应关系,一开始,这个表也是空的。

比如电脑A为了知道电脑B的MAC地址,将会广播一条ARP请求,B收到请求后,带上自己的MAC地址给A一个响应,此时A便更新了自己的ARP表。

电脑和路由器中都有ARP表用于缓存IP地址和MAC地址的映射关系,通过不断ARP协议请求完善起来的

1.4 传输层(Transport Layer)

经过链路层和网络层的努力,终于可以在不同电脑之间发送数据包,但是问题又来了,电脑里面有很多程序,数据包到了目的地之后到底应该交给哪个程序(进程)呢?

为了搞清楚到底是给哪个程序(进程)发数据,于是给每个进程分配了一个编号,即端口号。这样在发送的数据包中,也需要加上源端口号和目的端口号。 这样原本是两个电脑之间的通信,变成了两个进程之间的通信。

核心协议:传输控制协议 (TCP) 和用户数据报协议 (UDP)

功能:

  • TCP:面向连接、可靠的、基于字节流的传输层通信协议;处理数据的顺序发送、错误校正、流量控制
  • UDP:无连接服务;快速但无法保证每个数据包都能到达
  • 在端到端的通信过程中实现数据的正确分段和重组

1.5 应用层(Application Layer)

最上层就是应用层,电脑或手机使用的应用软件都是在应用层实现,应用层工作在操作系统的用户态,传输层及以下则工作在内核态。

应用层不需要去关心数据是怎么传输的,就类似于,我们寄快递的时候,只需要把包裹交给快递员,由他负责运输快递,我们不需要关心快递是如何被运输的。

所以,应用层只需要专注于为用户提供用户功能,比如HTTP、FTP、Telnet等

功能:

  • 为操作系统或应用程序提供网络服务
  • 包括所有高级协议(HTTP、FTP、SMTP 等)
  • 应用层协议通常基于传输层的 TCP 或 UDP 进行构建,依赖下层完成数据的实际传输

1.6 四层网络模型的层间交互

四层网络模型的各层通过以下方式进行交互:

  • 上层将数据交给下层进行传输
  • 下层在数据上添加适当的协议头和尾,然后将数据传递给物理网络
  • 当数据到达目标主机时,下层会剥离协议头和尾,然后将数据传递给上层

示例:当在浏览器中输入一个网址时,请求会通过以下各层:

  • 应用层:HTTP 协议处理请求并将其发送到传输层
  • 传输层:TCP 协议将请求分段并将其发送到网络层
  • 网络层:IP 协议添加目标 IP 地址并将其发送到链路层
  • 链路层:以太网协议将请求封装成数据帧并将其发送到网络

类似地,当响应从 Web 服务器返回时,它也会通过上述各层,直到到达您的浏览器。

1.7 四层网络模型每层使用的协议的示例

  • 应用层:HTTP、FTP、SMTP、DNS
  • 传输层:TCP、UDP
  • 网络层:IP、ICMP、ARP
  • 链路层:以太网、Wi-Fi、PPP

二、TCP 传输控制协议

2.1 TCP 传输控制协议简介

TCP的全称是 传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。TCP是工作在传输层上的传输层通信协议,职责是 实现主机间进程到进程的通信,其次还需要保证可靠性(不是安全性,换言之不能保证安全性)。

Tips: 用户数据报协议(UDP)是同一层内另一个重要的传输协议。

TCP面向连接意味着两个使用TCP协议的应用(通常一个客户端一个服务器)在彼此交换数据之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行通信,广播和多播不能用于TCP。

TCP的可靠性:无论网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端。TCP通过以下方式提供可靠性:

  • 应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度保持不变。由TCP传给IP的信息单位称为报文段或段。
  • 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  • 当TCP收到来自TCP另一端的数据,它将发送一个确认。这个确认不是立即发送,通常推迟几分之一秒。
  • TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
  • 既然TCP报文段作为IP数据报传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据重新排序,将收到的数据以正确的顺序交给应用层。
  • 既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。
  • TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲区,TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。

2.2 TCP 协议结构

TCP 报文是 TCP 层传输的数据单元,也叫报文段,TCP 报文的格式如下图所示: TCP协议格式 TCP报文段头部数据 两部分组成。

TCP报文段的头部 由 开头固定的20个字节长度 和 选项(除选项字段)两部分组成(如上图所示),下面来看几个重要的字段,或者说经常提到(面试经常被问的)的几个字段:

  • 源端口号目的端口号:端口号(16位)是为了标识具体由哪个应用程序来发送或接收数据包,一个IP地址和一个端口号也称为一个插口(socket)。TCP报文首部中的源端口号和目的端口号同IP数据报中的源IP与目的IP一同唯一确定一条TCP连接。目的端口和目的IP地址的作用是标识报文的接收地址。源端口和源IP地址的作用是标识报文的返回地址。

  • 序号和确认号:是TCP可靠传输的关键部分

    • 序号 seq:用来标识TCP发送端主机进程向TCP接收端主机进程发送的数据字节流,seq 表示在这个报文段中的第一个数据字节。在TCP传送的流中,每一个字节一个序号。在建立连接时由计算机生成的随机数作为其初始序号ISN,通过 SYN 包传给接收端主机,该主机要发送数据的第一个字节序号为这个ISN加1,因为SYN标志消耗了一个序号,每发送一次数据,就 累加 一次该次数据发送的 数据字节数 的大小。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。序号是用来解决网络包乱序问题。序号是32bit无符号数,序号到达2^32-1后又从0开始。

    • 确认序列号 ack:即ACK,用来确认确实有收到相关封包,内容表示期望收到下一个报文的序列号,用来解决丢包的问题。指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。发送端收到这个确认应答以后可以确认在这个序号以前的数据都已经被正常接收,该确认系列号用来解决不丢包的问题。

  • 数据偏移/首部长度:4bits,由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。

  • 保留:4bits。为将来定义新的用途保留,现在一般置0。

  • 标志位:CWR、ECN-Echo、URG、ACK、PSH、RST、SYN、FIN,每一个标志位表示一个控制功能。用来标识是什么类型的报文,比如SYN报文,ACK报文等。

    • CWR:Congestion window reduced,拥塞窗口减少。拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包。拥塞窗口是被TCP维护的一个内部变量,用来管理发送窗口大小。
    • ECN-Echo:显式拥塞提醒回应。当一个IP包的ECN域被路由器设置为11时,接收端而非发送端被通知路径上发生了拥塞。ECN使用TCP头部来告知发送端网络正在经历拥塞,并且告知接收端发送段已经受到了接收端发来的拥塞通告,已经降低了发送速率。
    • URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
    • ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。TCP 规定除了最初建立连接时的SYN包之外,建立连接之后,该位一般都是置为1。
    • PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
    • RST:重置连接标志,为1时,表示释放连接,重连。用于重置由于主机崩溃或其它原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
    • SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
    • FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
  • 滑动窗口大小:占16bits,用来告知发送端 接受端自己的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小是一个16bit字段,因而窗口大小最大为65535。

  • 校验和:占16bits。奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。

  • 紧急指针:占16bits。只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。

  • 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。

TCP报文段的数据部分 是可选的,在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

字段
长度
含义
Source Port 16比特 源端口,标识哪个应用程序发送。
Destination Port 16比特 目的端口,标识哪个应用程序接收。
Sequence Number 32比特 序号字段。TCP链接中传输的数据流中每个字节都编上一个序号。序号字段的值指的是本报文段所发送的数据的第一个字节的序号。
Acknowledgment Number 32比特 确认号,是期望收到对方的下一个报文段的数据的第1个字节的序号,即上次已成功接收到的数据字节序号加1。只有ACK标识为1,此字段有效。
Data Offset 4比特 数据偏移,即首部长度,指出TCP报文段的数据起始处距离TCP报文段的起始处有多远,以32比特(4字节)为计算单位。最多有60字节的首部,若无选项字段,正常为20字节。
Reserved 6比特 保留,必须填0。
URG 1比特 紧急指针有效标识。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。
ACK 1比特 确认序号有效标识。只有当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。
PSH 1比特 标识接收方应该尽快将这个报文段交给应用层。接收到PSH = 1的TCP报文段,应尽快的交付接收应用进程,而不再等待整个缓存都填满了后再向上交付。
RST 1比特 重建连接标识。当RST=1时,表明TCP连接中出现严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立连接。
SYN 1比特 同步序号标识,用来发起一个连接。SYN=1表示这是一个连接请求或连接接受请求。
FIN 1比特 发端完成发送任务标识。用来释放一个连接。FIN=1表明此报文段的发送端的数据已经发送完毕,并要求释放连接。
Window 16比特 窗口:TCP的流量控制,窗口起始于确认序号字段指明的值,这个值是接收端正期望接收的字节数。窗口最大为65535字节。
Checksum 16比特 校验字段,包括TCP首部和TCP数据,是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。
Urgent Pointer 16比特 紧急指针,只有当URG标志置1时紧急指针才有效。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。紧急指针指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)。
Options 可变 选项字段。TCP协议最初只规定了一种选项,即最长报文段长度(数据字段加上TCP首部),又称为MSS。MSS告诉对方TCP“我的缓存所能接收的报文段的数据字段的最大长度是MSS个字节”。
Padding 可变 填充字段,用来补位,使整个首部长度是4字节的整数倍。
data 可变 TCP负载。

2.3 TCP 连接的建立(三次握手)

TCP三次握手 是两个端点(通常称为客户端与服务器)建立可靠连接的过程,旨在确认双方的接收与发送数据能力,以及协商初始序列号等参数,确保数据传输的有序进行。在这个过程中,服务端有两个重要的队列:半连接队列(SYN queue)全连接队列(ACCEPT queue)

  • 半连接队列(SYN queue)

    • 当客户端发送SYN报文,服务器接收后进入SYN_RECV状态,此时服务端连接被放入半连接队列。
    • 队列长度由 tcp_max_syn_backlognet.core.somaxconn 和 业务中 tcp 调用 listen(fd, backlog)backlog 三者最小值决定。
  • 全连接队列(ACCEPT queue)

    • 当客户端(收到服务端回复的 SYN+ACK 报文)发送ACK报文后,服务器将连接从半连接队列移动到全连接队列,进入ESTABLISHED状态。
    • 队列长度由 net.core.somaxconnlisten(fd, backlog)backlog 两者最小值决定。

TCP连接建立的三次握手是由连接两端的三次报文传递来完成的,具体的流程:

  1. 当服务器执行 listen 操作时,它会初始化并配置 半连接队列 和 全连接队列 的容量。这一过程中,服务器会分配必要的内存资源,并设置队列的初始状态,以便能够管理和跟踪到达的连接请求。

  2. 客户端在执行 connect 操作时,首先会将其套接字状态设置为 TCP_SYN-SENT。随后,客户端会选择一个可用的本地端口,并构造一个 SYN 握手请求报文发送给服务器,同时,客户端还会启动一个重传定时器,以应对可能的网络延迟或丢包情况。

  3. 服务器在接收到客户端的 SYN 请求报文后,会检查其接收队列的状态。如果接收队列已满,服务器可能会拒绝该连接请求。如果队列有足够空间,服务器会发送 SYN+ACK 响应报文给客户端,并创建一个 request_sock 对象加入到半连接队列中,同时,服务器也会启动一个定时器,用于处理该半连接的超时情况。

  4. 客户端在收到服务器的 SYN+ACK 响应报文后,会停止之前设置的重传定时器,并更新其套接字状态为TCP_ESTABLISHED。接着,客户端会启动保活计时器,并发送 ACK 确认报文给服务器,客户端完成第三次握手。

  5. 服务器在接收到客户端的 ACK 确认报文后,会从半连接队列中移除之前的 request_sock 对象,并创建一个新的 sock 对象。这个新对象随后会被加入到全连接队列中,并将其状态设置为 TCP_ESTABLISHED,表示连接已完全建立。

  6. 最后,accept 系统调用会从全连接队列中提取一个已建立的连接,并将其返回给用户进程,以便进行后续的数据传输操作。

以下是三次握手过程中传递的具体报文及步骤:

  • 第一个报文(SYN):服务端处于 listen 等待连接状态,客户端主动发起连接请求(connect)。客户端会随机初始化序号(client_isn),将此序号(client_isn)置于 TCP 报文(新创建的空白报文)首部的 序号 字段中,同时把TCP报文的 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示主动向服务端发起连接,该报文不包含应用层数据,发送报文之后客户端处于 SYN-SENT 状态。

  • 第二个报文(SYN+ACK):服务端收到客户端的 SYN 报文,首先验证该报文确认客户端的连接请求(报文)有效,其次服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 报文(新创建的空白报文)首部的 序号 字段中,接着把TCP报文首部的 确认应答号 字段填入 client_isn + 1, 接着把TCP报文的 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。

  • 第三个报文(ACK):客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先将该应答报文 TCP 首部 ACK 标志位置为 1 ,其次 确认应答号 字段填入 server_isn + 1 ,最后把报文发送给服务端。之后客户端处于 ESTABLISHED 状态。服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。三次握手的最后一个报文(应答报文)可以携带客户端要发送到服务器的应用数据,如果该报文需要携带应用数据,则需设置该报文相应的标志位及其它信息,如 SYN 标志位置为 1,TCP 报文首部的 序号 字段 置位 client_isn + 1(第二个报文的 确认应答号)等。

Tips: ISN 初始化序列号(initial sequence number),是在建立tcp三次连接的时候,存储在序列号位置中的数字的代称。也就是说,告诉对方我将要开始发送的初始化序列号是多少,两边都要发这个ISN,即tcp三次连接中第一个SYN包和第二个SYN+ACK的包都有这个ISN。

至此,双方都已确认对方的接收与发送能力,且初始序列号已协商一致,连接建立完成,客户端与服务器进入 ESTABLISHED 状态,可以开始双向的数据传输。

TCP规定,SYN报文段不能携带数据,但要消耗一个序号;ACK报文段可以携带数据,但如果不携带数据则不消耗序号。

这里的首部header长度表示TCP头部有多少个32bit位,也就是多少个4字节,上图中是8,所以总共32字节,然后整个segment也是32个字节,说明payload的长度为0。

为什么是三次握手?不是两次、四次?

TCP建立连接时,通过三次握手能防止历史连接的建立(主要原因),能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

1、避免历史连接

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下,一个 旧 SYN 报文最新的 SYN 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接

如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:

  • 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接
  • 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接

所以,TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。

2、同步双方初始序列号

TCP 协议的通信的双方,都必须维护一个 序列号, 序列号是可靠传输的一个关键因素,它的作用:

  • 接收方可以去除重复的数据
  • 接收方可以根据数据包的序列号按序接收
  • 发送方可以标识发送出去的数据包中,哪些是已经被对方收到的

可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带 初始序列号 的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送 初始序列号 给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了 三次握手。而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

3、避免资源浪费

如果只有 两次握手,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

Tips: SYN攻击 在三次握手过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接(half-open connect)。此时服务器处于SYN_RECV状态,当收到ACK后,服务器转入ESTABLISHED状态。 SYN攻击 就是攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。SYN攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被SYN攻击: netstat -n -p TCP | grep SYN_RECV 一般较新的TCP/IP协议栈都对这一过程进行修正来防范SYN攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等,但是不能完全防范SYN攻击。

2.4 TCP 数据传输

TCP连接完成三次握手之后,就可以进行数据传输了,如下,电脑A和电脑B分别给对方发送了一次数据: 在数据传输阶段,序列号等于上次发送序列号+上次发送的长度len,确认序列号等于上次收到序列号+上次收到的长度len。

2.5 TCP 连接断开(四次挥手)

挥手过程中主机的角色:

  • 主动挥手方:主动断开连接的一方
  • 被动挥手方:被动断开连接的一方
  • 同时关闭:双方均属于主动挥手方

TCP四次挥手:

  • 第一次挥手:主动挥手方发送完数据后,向连接对端发送 FIN=1,seq=u(上次发送序号 + 上次发送数据字节长度) 关闭连接,发送完毕后主动挥手方从ESTABLISHED转换到FIN-WAIT-1 状态

  • 第二次挥手:被动挥手方 接收到关闭连接的FIN 包后,回复 ACK=1,ack=u+1(上次收到序号+1),发送完毕后被动挥手方从ESTABLISHED转换到CLOSE-WAIT 状态,主动挥手方接收到FIN 包的 ACK后从ESTABLISHED转换到FIN-WAIT-2 状态,此时 TCP连接处于半关闭状态,被动挥手方依旧可以给主动挥手方发送数据。

  • 第三次挥手:待被动挥手方发送完数据后,也向 主动挥手方 FIN=1,ACK=1, seq =w(上次发送序号 + 上次发送数据字节长度),发送完毕后服务器端进入LAST-ACK状态,客户端接收到后进入 TIME-WAIT状态

  • 第四次挥手:ACK=1,ack=w+1(上次收到序号+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME-WAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

TCP连接断开的四次挥手是由连接两端的四次报文传递来完成的,以下是四次挥手传递的具体报文及步骤:

  • 第一个挥手报文: 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN-WAIT-1 状态
  • 第二个挥手报文: 服务端收到第一个挥手报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态,客户端收到服务端的 ACK 应答报文(第二个挥手报文)后,之后进入 FIN-WAIT-2 状态
  • 第三给挥手报文: 服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST-ACK 状态
  • 第四个挥手报文: 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME-WAIT 状态,服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭;客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

为什么挥手需要四次?

回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。

服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

2.6 TCP 状态转换图

TCP状态机是TCP连接的变化过程,TCP 在三次握手和四次挥手的过程,就是一个 TCP 的状态说明,由于 TCP 是一个面向连接的、可靠的传输,因此在数据传输之前需要建立连接连接,传输完成后需要关闭的过程,无论是哪个方向的传输,必须建立连接才行,在双方通信的连接建立过程中,TCP 的状态是不一样的。 这张图展示了 TCP(Transmission Control Protocol,传输控制协议)的状态转移图,描述了 TCP 连接在不同阶段之间的状态变化和相互转换。

TCP 状态图解释

  • CLOSED(关闭状态):初始时没有任何连接的状态,表示 TCP 连接未建立或已关闭。

  • LISTEN(监听状态):服务端进入的状态,表示服务端正在监听来自客户端的连接请求(等待 SYN 报文段)。

  • SYN-SENT(同步已发送状态):客户端进入的状态,客户端 socket 执行 connect 连接操作,向服务端发送 SYN 报文段,之后等待来自服务器的 SYN+ACK 报文段(服务器的连接请求和对客户端连接请求的确认)。

  • SYN-RCVD(同步已接收状态):服务端进入的状态,表示服务端已接收到客户端发送的 SYN 报文段,并发送了 SYN+ACK(同步+确认)报文段作为响应,等待客户端的 ACK(确认)报文段。

  • ESTABLISHED(已建立状态):表示 TCP 连接已经建立成功,客户端发送了最后一个 ACK 报文段后进入此状态,服务端接收到 ACK 报文段后进入此状态,进入此状态后,客户端和服务端就可以进行数据传输(发/收数据报文段)。

  • FIN-WAIT-1(终止等待状态1):主动断开的一端进入的状态,表示主动断开端已经发首先送了 FIN(连接关闭请求报文段,四次挥手中的第一个 FIN)报文段,等待被动断开端对该 FIN 的 ACK(确认)报文或进一步的数据传输报文段。

    Tips:

    • 主动断开端: TCP连接两端首先发送了 FIN(socket 执行 close 关闭连接)请求报文段的一端, 一般是 客户端;
    • 被动断开端: TCP连接两端首先接收到 FIN(关闭连接请求报文段)的一端,一般是 服务端;
  • CLOSE-WAIT(关闭等待状态):被动断开端进入的状态,被动断开端收到了主动断开端的 FIN(连接关闭请求,四次挥手中的第一个 FIN)报文段,并已经发送出 ACK(确认,四次挥手中的第一个 ACK)报文段。表示已经收到了主动断开端发送的断开请求,但是本方是否立即断开连接(发送 FIN 报文段)取决于是否还有数据需要发送给主动断开端,若还有数据要发送,则在发送 FIN 报文段之前均为此状态。

  • FIN-WAIT-2(终止等待状态2):主动断开端进入的状态,表示主动断开端已经收到了被动断开端对主动断开段首先发送的 FIN(连接关闭请求,四次挥手中的第一个 FIN)报文段的 ACK(确认,四次挥手中的第一个ACK)响应报文段,主动断开端继续等待接收被动断开端发送 FIN(连接关闭请求、四次挥手中的第二个 FIN)报文段。此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。

  • LAST-ACK(最后确认状态):被动断开端进入的状态,表示被动断开端发送了FIN(连接关闭请求、四次挥手中的第二个 FIN)报文段,等待主动关闭端回复 ACK(确认,四次挥手中的第二个ACK)报文段,被动关闭端收到主动关闭端的 ACK(确认,四次挥手中的第二个ACK)响应报文段后,被动断开端将正式 进入连接关闭状态。

  • CLOSING(关闭状态):表示在关闭过程中出现异常,客户端和服务端都同时发送了连接关闭请求(FIN)导致的状态。当主动关闭方处于 FIN-WAIT-1 时,被动关闭方发送的 FIN 先于之前的自己发送的 ACK (对主动关闭方发生的 FIN 的应答报文)到达,主动关闭方就直接从 FIN-WAIT-1 -> CLOSING,(其实就相当于同时关闭),然后迟来的 ACK 到达时,主动关闭方就从 CLOSING -> TIME-WAIT。

  • TIME-WAIT(时间等待状态):主动关闭端的一个状态,表示连接已经关闭,主动断开端收到了被动断开端的 FIN(连接关闭请求,四次挥手中的第二个 FIN)报文段,并已经发送出 ACK(确认,四次挥手中的第二个 ACK)响应报文段做最后的确认,但为了确保网络中所有数据包都被正确处理,主动关闭端等待一段时间(2MSL,最长报文段寿命的两倍)后才彻底释放连接。

    Tips: 为什么 TIME_WAIT 状态还需要等 2MSL 后才能返回到 CLOSED 状态? 这是因为虽然TCP连接双方都同意关闭连接了,而且握手的 4 个报文也都协调和发送完毕,按理可以直接回到 CLOSED 状态(就好比从 SYN_SEND 状态到 ESTABLISH 状态那样);但是因为网络实际是不可靠的,我们无法保证最后发送的 ACK 报文一定会被对方收到,因此对方处于 LAST_ACK 状态下的 sock 可能会因为超时未收到 ACK 报文而重发 FIN 报文,所以这个 TIME_WAIT 状态的作用就是用来重发(再次收到对端的 FIN 报文段)可能丢失的 ACK 报文。

状态转移说明

  • 连接建立过程:从 CLOSED 状态开始,1、客户端发送 SYN;2、服务端收到 SYN 后回复 SYN+ACK;3、客户端收 SYN+ACK 后回复ACK 后进入 ESTABLISHED 状态,服务端收到ACK 后也进入 ESTABLISHED 状态,表示连接已建立。
  • 连接关闭过程:在数据传输完成后,一方发送 FIN 报文段,另一方收到后发送 ACK 确认,然后进入 FIN-WAIT-1(客户端)或 CLOSE-WAIT(服务端),接着根据情况进入 FIN-WAIT-2、LAST-ACK 和 TIME-WAIT 等状态,最终连接彻底关闭。

SYN 和 ACK 报文

  • SYN(同步):SYN 是 TCP 协议中用于建立连接的同步序列号(Synchronize Sequence Number)报文段。当客户端向服务端发起连接请求时,客户端会发送一个 SYN 报文段,其中包含一个随机的初始序列号(Sequence Number),表示客户端希望和服务端建立连接。
  • ACK(确认):ACK 是 TCP 协议中用于确认收到数据或状态的报文段。在 TCP 连接建立过程中,服务端收到客户端发送的 SYN 报文段后,会回复一个 SYN+ACK 报文段,其中包含确认号(Acknowledgment Number)和服务端的初始序列号。服务端的 SYN 报文段中的 ACK 标志位被设置为 1,表示服务端已经收到了客户端的 SYN 报文段,并且同意建立连接。

Tips: 服务端正常状态变迁

  1. 服务器通过listen()系统调用后进入 LISTEN 状态,被动等待客户端连接(被动打开)。
  2. 服务器一旦监听到某个连接请求(收到同步报文段 SYN),就将该连接放入内核连接请求等待队列中,并向客户端发送 SYN+ACK 同步确认报文段,此时服务端处于 SYN-RCVD 状态,等待客户端的确认。
  3. 服务器成功接收到客户端发送回的 ACK 确认报文段后,进入 ESTABLISHED状态(此时完成三次握手建立好了连接,双方能够进行双向数据传输)。
  4. 数据传输完毕,服务器收到客户端发送的 FIN 结束报文段后,并给客户端发送回 ACK 确认报文段(服务器可能还有数据要发),此时服务端处于 CLOSE-WAIT状态,服务器继续发送未发完的数据。
  5. 服务器在发送完数据后也给客户端发送 FIN 结束报文段,之后进入 LAST-ACK状态,等待客户端对结束报文段的最后一次 ACK 确认。
  6. 服务器成功收到客户端发送回的 ACK 确认报文段后,连接彻底关闭,进入 CLOSE状态

Tips: 客户端正常状态变迁

  1. 客户端通过connect()系统调用给服务器发送一个 SYN 同步报文段请求建立连接,此时客户端进入 SYN-SENT状态
  2. 客户端成功收到服务器发送回的 SYN+ACK 同步确认报文段后,再给服务器发送一个 ACK 确认报文段,之后进入 ESTABLISHED状态。(此时客户端认为已建立连接,双方能够进行双向数据传输)
  3. 数据传输完毕后,客户端给服务器发送一个 FIN 结束报文段,之后进入 FIN-WAIT-1状态,等待服务端的确认。
  4. 客户端收到服务器发送的 ACK 确认报文段后,进入 FIN-WAIT-2状态,之后接收服务器发送的还未发完的数据。
  5. 服务器发完数据后向客户端发送 FIN 结束报文段,客户端收到服务器发送的 FIN 结束报文段,同时给服务端发回一个 ACK 确认报文段,之后客户端进入 TIME-WAIT状态
  6. TIME-WAIT状态 下等待2MSL时长内,如果客户端没有收到服务器重传的 FIN结束报文段,则连接彻底关闭,此时客进入 CLOSE状态

TCP特殊状态变迁:

  • SYN-SENT状态 —> CLOSE状态

    • connect连接的目标端口不存在(未被任何进程监听)。
    • connect连接的目标端口仍被处于TIME-WAIT状态的连接所占用,则服务器给客户端发送RST复位报文段,重新请求连接,但如果最终没能成功,则因超时而导致connect调用失败。
    • 如果目标端口存在,但connect在超时时间内未收到服务器的确认报文段,则connect调用失败。
  • LISTEN状态 —> SYN-SENT状态

    • 服务器主动向客户端发起连接。
  • SYN-RCVD状态 —> LISTEN状态

    • 服务器如果收到RST报文段,说明客户端请求重新建立连接,所以服务端返回LISTEN状态。
  • SYN-RCVD状态 —> FIN-WAIT-1状态

    • 服务器是接收到客户端发送 SYN 同步报文段后,向客户端发送回 SYN+ACK 同步确认报文段后进入 SYN-RCVD状态,之后再发送FIN结束报文段。
    • 正确情况下客户端先收到 SYN+ACK同步确认报文段,同时发送一个ACK确认报文段给服务器,客户端之后就进入ESTABLISHED状态。之后才会收到FIN结束报文段,客户端是已经建立连接的状态,并且服务端也将会因收到ACK确认报文段而进入ESTABLISHED状态。
    • 所以如果处于SYN-RCVD状态的服务端想要结束连接,则需要发送FIN结束报文段来按照正常流程解除连接。因此,SYN-RCVD服务器主动发送FIN结束报文段,进入FIN-WAIT-1状态。
  • SYN-SENT状态 —> SYN-RCVD状态 (同时打开连接)

    • 客户端向服务器发送完SYN同步报文段后进入SYN-SENT状态,等待服务器发送的确认,但是却收到服务器发送的SYN同步报文段。说明双方在接收到对方的SYN同步报文段之前,都进行了主动发起请求连接的操作,即向对方发出SYN同步报文段。所以对方都会先收到SYN同步报文段而不是预想的ACK确认报文段,因此都进入SYN-RCVD状态。
    • 两端的状态变化都是由 CLOSED —> SYN-SENT —> SYN-RCVD —> ESTABLISHED
  • FIN-WAIT-1状态 —> CLOSING状态 (同时关闭连接)

    • 客户端向服务器发送完FIN结束报文段后进入FIN-WAIT-1状态,等待服务发送的确认,但是却收到服务器发送的FIN结束报文段。说明双方在接收到对方的FIN结束报文段之前,都进行了主动发起结束连接的操作,即向对方发出FIN结束报文段。所以对方都会先收到FIN结束报文段而不是预想的ACK确认报文段,因此都进入CLOSING状态。
    • 两端的状态变化都是由 ESTABLISHED —> FIN-WAIT-1 -> CLOSING —> TIME-WAIT —> CLOSED
  • FIN-WAIT-1状态 —> TIME-WAIT状态

    • 处于 FIN_WAIT1 状态的客户端直接收到 ACK FIN 确认结束报文段(而不是先收到ACK确认报文段,再收到 FIN 结束报文段,说明服务端在客户端请求结束连接时没有消息要发)。

2.7 TCP 每个连接状态可能出现的问题

在TCP连接的三次握手过程中,每个状态都有可能出现不同的问题,下面将分析每个状态可能出现的问题,并结合案例分析给出相应的排查命令。

  1. SYN-SENT 状态
  • 问题:客户端发送了SYN报文后,如果长时间没有收到服务器的SYN-ACK回复,客户端可能会重传SYN报文或者最终超时放弃连接。

  • 案例分析:可能因为网络延迟、服务器处理慢或者SYN洪水攻击导致服务器没有及时响应。

  • 排查方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#查看本地的TCP连接状态,检查SYN-SENT的数量是否异常
netstat -ant或ss -ant

# 查看当前的系统设置
sysctl net.core.somaxconn
# 说明:
# 该参数指定了系统中所有套接字监听队列的最大长度。
# 当一个连接请求到达时,如果所有的队列都已满,新的连接请求会被拒绝或丢弃。
# 这个参数影响到所有类型的套接字,而不仅仅是 TCP 套接字。

sysctl net.ipv4.tcp_max_syn_backlog
# 说明:
# 该参数指定了 TCP 半连接队列的最大长度。
# 在三次握手过程中,服务器收到客户端发送的 SYN 包后,将会放置在半连接队列中等待连接建立完成。
# 如果半连接队列已满,服务器将无法接受新的连接请求,导致客户端的连接请求被丢弃。
  1. SYN-RCVD状态
  • 问题:服务器收到了SYN报文并响应了SYN-ACK,但客户端没有收到或者没有正确响应ACK报文,连接无法建立。

  • 案例分析:可能因为网络丢包、客户端处理延迟或者服务器端的半连接队列已满导致。

  • 排查方法:使用 netstat -an | grep SYN_RECEIVED 查看服务器端的半连接状态,检查是否有大量 SYN-RCVD 状态的连接。

  1. ESTABLISHED状态
  • 问题:连接建立后,如果一方尝试发送数据但另一方没有响应,可能会导致连接异常。

  • 案例分析:可能是因为网络故障、对方应用程序崩溃或者防火墙/安全策略阻止了数据传输。

  • 排查命令:使用 netstat -an | grep ESTABLISHED 查看已建立的连接,结合 lsof -i :port 检查端口上的应用程序状态。

  1. FIN_WAIT状态
  • 问题:当一方关闭连接(发送FIN报文)后,如果另一方没有及时响应(发送ACK报文),可能会导致连接长时间处于FIN_WAIT状态。

  • 案例分析:可能是因为对方应用程序处理慢或者网络延迟导致。

  • 排查命令:使用 netstat -an | grep FIN_WAIT 查看 FIN_WAIT 状态的连接,检查是否有异常的连接长时间未关闭。

  1. CLOSE-WAIT状态
  • 问题:当一方关闭连接后,另一方只是发送了ACK报文但没有关闭自己的连接,导致连接长时间处于CLOSE-WAIT状态。

  • 案例分析:可能是因为对方应用程序没有正确处理关闭信号或者全连接队列已满导致。

  • 排查命令:使用 netstat -an | grep CLOSE-WAIT 查看 CLOSE-WAIT 状态的连接,检查是否有大量的此类状态连接。

  1. LAST-ACK状态
  • 问题:当服务器端关闭连接后,等待客户端的最终ACK确认,如果客户端没有及时发送ACK,服务器端会长时间处于LAST-ACK状态。

  • 案例分析:可能是因为客户端处理慢或者网络延迟导致。

  • 排查命令:使用 netstat -an | grep LAST-ACK 查看LAST-ACK状态的连接,检查是否有异常的连接。

  1. TIME-WAIT状态
  • 问题:连接关闭后,为了确保最后一个ACK报文能够到达对方,会进入TIME-WAIT状态等待一段时间(2MSL),如果等待时间过长,可能会占用系统资源。

  • 案例分析:可能是因为网络延迟或者系统设置不当导致。

  • 排查命令:使用 netstat -an | grep TIME-WAIT 查看TIME-WAIT状态的连接,检查是否有大量的此类状态连接。

针对上述状态中可能出现的问题,都可以通过调整系统参数(如 tcp_max_syn_backlog、net.core.somaxconn等)来优化性能,同时结合 netstatss 等工具进行实时监控和排查。

2.8 TCP 连接队列维度的异常

  1. 半连接队列已满
  • 问题:当半连接队列(SYN队列)已满时,服务器将无法处理新的SYN请求,导致新的连接尝试失败。

  • 系统处理:可以通过调整 net.core.somaxconn 参数来增加队列的大小。此外,tcp_max_syn_backlog 参数也会影响半连接队列的长度,它定义了系统可以同时为还未完成三次握手的连接保留多少个半连接队列位置。

  • 排查命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 1. 查看本地的TCP连接状态,检查SYN-SENT的数量是否异常
netstat -ant
# 或
ss -ant

# 2. 查看当前的系统设置
sysctl net.core.somaxconn
# 该参数指定了系统中所有套接字监听队列的最大长度。
# 当一个连接请求到达时,如果所有的队列都已满,新的连接请求会被拒绝或丢弃。
# 这个参数影响到所有类型的套接字,而不仅仅是 TCP 套接字。

sysctl net.ipv4.tcp_max_syn_backlog
# 该参数指定了 TCP 半连接队列的最大长度。
# 在三次握手过程中,服务器收到客户端发送的 SYN 包后,将会放置在半连接队列中等待连接建立完成。
# 如果半连接队列已满,服务器将无法接受新的连接请求,导致客户端的连接请求被丢弃。
  1. 全连接队列已满
  • 问题:全连接队列(ACCEPT队列)已满意味着服务器已经建立了连接,但由于应用程序没有及时调用 accept() 系统调用来处理新的连接,导致新的连接请求无法被接受。

  • 系统处理:可以通过调整 somaxconn 参数来增加全连接队列的大小。此外,tcp_abort_on_overflow 参数决定了当全连接队列溢出时,系统是丢弃ACK包(值为0)还是发送RST包给客户端(值为1)。

    • tcp_abort_on_overflow = 0

      • 含义:当全连接队列溢出时,系统不会主动向客户端发送RST包来终止连接。相反,它会简单地丢弃来自客户端的ACK包。这意味着客户端的连接请求被忽略,而不是被明确拒绝。
      • 系统影响:客户端可能会重试连接,这可能导致网络流量增加,但不会立即终止连接尝试。服务器上的应用程序可能会继续处理已经接受的连接,但新的连接请求会被挂起,直到队列中有空间。这可能导致客户端体验到延迟或超时。
    • tcp_abort_on_overflow = 1

      • 含义:当全连接队列溢出时,系统会向客户端发送一个RST包,明确拒绝新的连接请求。这是一种更为积极的处理方式,可以立即终止客户端的连接尝试。
      • 系统影响:客户端会收到一个错误信号,通常是connection reset by peer,表明连接已被服务器重置。这会导致客户端立即停止尝试连接,并可能触发重试逻辑或错误处理机制。这种方式可以减少无效的连接尝试,减轻服务器的网络流量压力,但可能会增加客户端处理错误的复杂性。
    • 在实际应用中,选择哪种策略取决于具体的业务需求和网络环境。如果服务器能够较快地处理现有连接,或者希望避免客户端频繁重试,可能会选择tcp_abort_on_overflow = 1。如果希望尽量减少对客户端的影响,或者认为客户端能够妥善处理超时和重试逻辑,可能会选择tcp_abort_on_overflow = 0。

  • 排查命令:排查全连接队列溢出的情况时,可以通过以下命令查看相关参数的当前值和统计信息:

1
2
3
4
5
6
7
8
# 查看和设置 somaxconn 的值,它决定了全连接队列的最大长度
sysctl net.core.somaxconn

# 查看 tcp_abort_on_overflow 的当前值。
cat /proc/sys/net/ipv4/tcp_abort_on_overflow`
# 查看全连接队列溢出的次数,这可以帮助确定问题是否正在发生,以及发生的频率。
netstat -s | grep "overflowed"

三、TCP重传机制

3.1 TCP 确认机制 ( acknowledge )

TCP通过确认机制 ( acknowledge ) 保证了信息的成功发送,发送方发送出了 一个 TCP报文段(此时发送方不知道接收方是否收到了该 报文段),所以需要接收方回复一个 ACK确认 报文。发送方发出数据后如果收到了接收方的确认信息(ACK确认报文段),表示对方收到了数据;如果没有收到接收方的确认报文信息,则发送方认为对方还没有收到数据(可能 1.发送报文数据还在传输途中、2.接收方发送的确认报文数据还在传输途中、3.发送报文数据在传输途中丢失 或 4.接收方回复的确认报文数据在传输途中丢失 四中情况)。

如果发送方连续发送了很多分数据,如何知道对方确认收到的是哪一份?此时就需要发送方对发送的每一份数据进行编号,同时接收方反馈的确认消息也带上接收到的数据编号信息,这样接收方收到数据的时候,就能知道收到的是哪一份数据,然后给发送方反馈收到数据的确认信息,发送方收到确认信息时也就知道接收方收到哪些数据。

如果发送方没有收到某一份数据的确认,就重新发送信息,一般超过一定时间没有收到确认才进行重发,因此也称为超时重发机制。

Tips: TCP具体是通过怎样的方式来保证数据的顺序化传输? 主机每次发送数据时,TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收主机对分配的这个序列号进行确认,如果发送主机在一个特定时间内没有收到接收主机的确认,则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收主机一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层(应用层)进行处理。

具体步骤如下: (1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区; (2)并为每个已发送的数据包启动一个超时定时器; (3)如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区; (4)否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。 (5)接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。

3.2 TCP 的数据编号(seq)和确认编号(ack)

电脑A给电脑B发送数据,在网络传输中,由于网络的不可靠,数据包可能在半路丢失,既然TCP是可靠传输的协议,就必须要解决这个问题,其实只要解决两个事情

  • 让A知道数据包丢了
  • 丢了的数据包要重传

于是 B 每接收到一个数据包(报文)后,都要给 A 发送一个ACK确认,即告诉 A 自己已经收到了,如果 A 在一定时间内没有收到 B 返回的 ACK,那 A 就认为是数据包丢了,就要重新发送,这就是 TCP数据传输的 确认应答机制超时重传机制,通过 确认应答机制 + 超时重传机制 就能解决丢包的问题。

在TCP数据传输的 确认应答机制中,如果 A 每次发数据包之前,必须先收到了上个数据包的ACK才可以,这样效率太慢了。 为了提高效率,允许发送方发送多个数据包,像下面这样,电脑A在收到ACK之前发送了三个数据包,这样就比停等协议的效率提高了三倍,把这种技术称为流水线。

这样又出现了新的问题,在网络传输过程中,并不能有条不紊地像上面这样进行数据传输,比如在上面这个流水线的图中,电脑A收到了三个ACK,但是它并不能确定收到的ACK是对应于哪个数据包的。

于是,电脑A在发送数据包时,为每个数据包都增加一个序号(seq),电脑B回应ACK时,也对应增加一个确认号(ack).

发送的数据编号被称为 序列号(Sequence Number - SN)

确认的数据编号被称为 确认序列号(Ackonwledge Sequence Number - ASN)

编号的规则:每个字节都要占用一个号,发送时的起始编号称为初始序列号(Initial SN - ISN),ISN不一定为0,而是一个随机值,不过编号是连续的,因此可以记为0(相对值)。

SN 在发送TCP Segment 的 header中如何体现:TCP 发送/接收的完整数据,一般称为segment(段),TCP segment = header + payload

当一次性发送一些数据的时候,SN只需要填写本次发送的数据中的第一个字节的数据即可,因为编号是连续的,且segment会携带payload长度

TCP协议正是通过序列号保证了传输顺序,TCP由于要进行发送,也要进行确认,所以实际上TCP Segment有两种不同的角色:

  • send segment
  • acknowledge segment

TCP 设计的时候,一个segment可以身兼两种不同的角色,无论什么时候,一个segment都视为send segment的角色,而当ACK标志位被置位(置1)时,该segment具备了acknowledge segment的角色。 TCP协议格式

在TCP Header 中,通过ACK的标志位处理ack角色,一个ack只占据一个bit位的数据,也就是说,ack的值为0或1,为1的时候就是被置位了,表示ASN字段有意义,如果为0,则无论ASN为多少,其字段都无意义。

TCP规定,在连接建立后所有传送的报文段都必须把ACK置1

序列号 的填写规则:填写自连接建立开始以来将要发送的数据的第一个字节编号(本次发生数据的第一个字节编号)。

确认序列号 的填写规则:填写自连接建立开始以来将要接收的下一个字节的数据的编号(本次收到的数据的最后一个字节编号的下一个)。

3.3 累积确认

前面说过,ack并不是和seq一一对应的。 比如在上面这个图中,按流水线发送seq1 ~ seq5数据包,但传输过程中seq2丢失了,其他都送达电脑B,此时电脑B回应确认序列号ack为2,表示seq 2之前的已经全部接收,期望可以收到seq2,等电脑A重新将seq2发送到电脑B之后,电脑B回应确认序列号ack为6,表示seq6之前的已经全部接收,期望下一个可以收到seq6。

这就是 累积确认

3.4 TCP 超时重传机制

在 TCP 连接的报文发送过程中,如果发送方没有收到接收方发送的应答,可能的情况有:

  • 接收方没有收到发送的数据,所以没有应答,接收方没收到的原因可能很多,比如数据还在路上,但是还未到达,或者数据已经在路上丢失了,永远不可能到达接收方。
  • 接收方收到发送的数据,也应答了,但发送方没有收到,发送方没收到的应答的原因,比如应答还在路上,但是还未到达,或应答已经丢失了。

数据或者应答还在路上的情况,可以通过一定的超时机制解决该问题。

如果数据在发送的过程中就丢包了,重发肯定没有问题,如果数据发送到对方了,但是应答在路上丢失了,这个时候的重发,可能会导致对方收到重复的数据,不过也没关系,通过序列号就能判断这个数据是不是重复的,TCP会保存SN,如果收到的信息的SN已经存在了,那就直接丢弃,如果不存在,说明是新的数据,那就保存并发送应答。

比如电脑A向电脑B发送数据时,设置一个定时器,当在指定的时间内没有收到电脑B的ACK确认应答,就会重新发送该数据。

以下两种情况会发生超时重传:

  • 数据包丢失
  • 电脑B收到了数据包,但是确认应答丢失

Tips: 这里的一定时间就是超时时间,即在这个时间段内都没有收到ACK,就需要重传。 如果是第二种情况,即确认应答丢失,则电脑B在收到电脑A重传的数据(如1 ~ 1000)后,由于之前已经接收过,再有相同的数据送达时会丢弃。这里的1 ~ 1000指的是第1字节到第1000字节的意思。

超时重传中的这个超时时间怎么确定?

在此之前,先来了解一下什么是RTT(Round Trip Time)

  • RTT就是从发送数据包开始,到接收到ACK,这之间的时间间隔,也就是数据包的往返时间。需要注意的是,RTT并不是一个定值,它是在波动的,TCP在每次发包时都会计算往返时间,再使用这个往返时间加上一个偏差值得到超时时间

超时重传时间叫RTO(Retransmission Timeout),超时重传时间应当比RTT稍微大一点。如果RTO比RTT大太多,那么就不能及时地重传,如果RTO比RTT小,那么ACK还在路上的时候,就又发了一次数据包,造成不必要地重传。 在实际网络环境中,RTT是经常变化的,所以RTO也是一个动态变化的值

如果超时重传的数据,再次超时又需要重传的时候,超时时间加倍。此外丢失的数据不会无限制地重发,达到一定重发次数后,就会认为网络发生异常,强制断开连接。

举几个超时重传的例子:

  • 例子1:电脑A连续发送两个数据包,seq1和seq2,两个数据包都成功送达电脑B,电脑B回应了两个确认应答,ack2和ack3,假设两个应答包都超时了,此时电脑超时重传seq1,重新设定定时器,此时,只要ack3在新的超时发生以前到达,则seq2就不会被重发。

  • 例子2:电脑A连续发送两个数据包,seq1和seq2,两个数据包都成功送达电脑B,电脑B回应了两个确认应答,ack2和ack3,假设ack2丢失,ack3在超时发生前送达电脑A,此时电脑A知道seq1和seq2都成功送达,所以电脑A不会重新发送seq1。

3.5 快速重传

超时重传存在的问题是超时周期可能相对较长,那么重新发送丢失的数据包之前,必须等待够超时时间才行。

快速重传是另一种重传机制,如下: 在上图中,电脑A发送seq1 ~ seq5数据包

  • seq1先送达,因此电脑B回应确认序号ack 2
  • seq2丢失,seq3、seq4和seq5都送达,电脑B回应确认序号ack还是 2
  • 电脑A收到电脑B发送的三次 ack 2之后,知道电脑B没有收到seq2,此时即便还没有超时,也会重传
  • 电脑A重新发送了seq2,此时因为seq3、seq4和seq5都收到了,于是回应确认序号 ack 6

所以快速重传就是,当收到三个相同的 ack 时,即便还没有超出超时时间,也会重传丢失的数据。

3.6 选项SACK

如电脑A向电脑B发送6个数据,seq1 ~ seq6,其中只有seq2和seq3丢失,电脑B重复发送ack2,表示未收到seq2,但是此时电脑A只知道电脑B没有收到seq2,电脑A不知道seq3 ~ seq6是否已经收到,此时电脑A应该只发送seq2还是发送seq2之后所有的数据?

如果只重传seq2,那么对于丢失的seq3,还要在收到三个重复的ack3才会重传 如果重传seq2之后的所有数据,对于seq4 ~ seq6就重复传输了 如果电脑A确切地知道哪些数据包丢失了,就只重新传输丢失地数据包就好了,使用SACK可以解决这个问题。

其实就是在TCP头部 ‘选项’ 字段里加上SACK这样一个东西。 通过SACK,电脑B可以告诉电脑A哪些收到了,哪些没收到,这样,电脑A只传输没收到的数据即可。 在上图中,电脑A收到了三次同样的ack 2,于是就触发快速重传机制,通过SACK发现只有seq2丢失,于是只重发seq2。

Tips: 需要注意的是,发送和接收双方都需要支持SACK

四、TCP滑动窗口、流量控制 和 拥塞控制

4.1 TCP滑动窗口

窗口 是一段被发送者发送的连续的字节序列,窗口大小 指⽆需等待确认应答,⽽可以继续发送数据的最⼤值。窗⼝的⼤⼩通常由接收⽅的窗⼝⼤⼩来决定。发送⽅发送的数据⼤⼩不能超过接收⽅的窗⼝⼤⼩,否则接收⽅就⽆法正常接收到数据。

滑动窗口 是一个大小可变的窗口,左右两端方向一致的向前滑动

发送方的滑动窗口 主要用于控制发送数据的流量,窗口的大小由接收方决定,接收方通过TCP头部中的窗口大小字段来通知发送方目前还可以接收多少字节的数据。 发送方在收到接收方的窗口大小后,会根据窗口大小来控制发送的数据量

  • 如果窗口大小为0,则发送方将停止发送数据,直到接收到非0的窗口大小通知;
  • 如果窗口大小不为0,则发送方可以继续发送数据,但不能超过窗口大小所允许的范围;

接收方的滑动窗口 主要用于控制接收数据的流量,窗口的大小由接收方根据自身的处理能力和网络状况来决定,并通过TCP头部中的窗口大小字段来通知发送方。

接收方在收到发送方发送的数据后,会将其放入接收缓冲区中,并通过ACK确认号来通知发送方哪些数据已经被成功接收,接收方在发送确认号时,会将窗口大小字段设置为接收缓冲区中还可以接收的数据量,从而控制发送方发送的数据量

  • 如果接收缓冲区已满,接收方会将窗口大小设置为0,暂停接收数据
  • 直到缓冲区中有足够的空间可以继续接收数据

4.2 TCP 流量控制

TCP 流量控制 是指让发送⽅根据接收⽅的实际接收能⼒控制发送的数据量。

Tips: 流量控制为端到端的控制

4.3 拥塞控制

拥塞控制的⽬的是避免发送⽅的数据填满整个⽹络,只要发送⽅没有在规定时间内接收到 ACK 应答报⽂,也就是发⽣了超时重传,就会认为⽹络出现了拥塞

出现拥塞的条件:对资源需求的总和 > 可用资源

网络中有许多资源同时呈现供应不足—>网络性能变坏—>网络吞吐量将随输入负荷增大而下降

拥塞控制:防止过多的数据注入到网络中。

TCP的拥塞控制由几个个核心算法组成:

1、慢启动(Slow Start)

在开始发送数据时,先发送少量的数据探路。探清当前的网络状态如何,再决定多大的速度进行传输。

慢启动的算法如下:

  • 刚连接好时,先初始化cwnd = 1,表示可以传一个MSS大小的数据
  • 当收到这一个ACK,cwnd增加到 2
  • 接着发送2个数据段,如果成功收到相应的ACK,cwnd由2变4
  • 接着发送4个数据段,如果成功收到相应的ACK,cwnd由4变8
  • 以此类推 所以慢启动算法中,发包数量会呈指数增长,但是不会无限制增长,ssthresh(slow start threshold)就是慢启动的上限,当cwnd>=ssthresh时,就会继续使用拥塞避免算法(下面会讲)来计算cwnd。

达到慢启动⻔限ssthresh,由慢启动转为拥塞避免

2、拥塞避免(Congestion avoidance) 从慢启动可以看到,cwnd可以很快的增长上来,从而最大程度利用网络带宽资源,但是cwnd不能一直这样无限增长下去,一定需要某个限制。TCP使用了一个叫慢启动门限(ssthresh)的变量,当cwnd超过该值后,慢启动过程结束,进入拥塞避免阶段。

当 swnd >= ssthresh时,就会进入拥塞避免算法,一般来说ssthresh的值是65535,单位是字节,当cwnd达到这个值后,算法如下:

收到一个ACK时,cwnd = cwnd + 1/cwnd

为了更容易用图表示,我假设ssthresh是8,当cwnd == 8时,使用拥塞避免算法,当8个ACK都到来时,每个ACK增加1/8,8个一共增加1,以此类推,因此拥塞避免算法是线性增长,如下: cwnd按照这样一直增长,网络就会慢慢出现拥塞的情况,就会出现丢包,此时就会进入拥塞发生时的算法。

3、拥塞状态时的算法 在出现了丢包的情况,拥塞发生,于是发送端开始减缓发送速率。

超时重传和快速重传使用的算法是不一样的,先说超时重传,当发生超时重传时,TCP认为这种情况比较糟糕,于是迅速改变降低cwnd避免网络更加拥塞。

  • sshthresh = cwnd / 2
  • cwnd重置为1
  • 进入慢启动过程

接着上面的图,假设cwnd == 12时,发生了超时重传,就会进入慢启动算法,如下:

4、快速重传 (Fast Retransmit) 除了超时重传,还有 快速重传 (Fast Retransmit),快速重传算法要求首先接收方收到一个失序的报文段后就立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。

当发生快速重传时,cwnd和sshthresh会更新如下,然后就进入快速恢复算法。

  • cwnd = cwnd / 2
  • sshthresh = cwnd
  • 进入快速恢复算法

5、快速恢复(Fast Recovery)

当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。

再收到重复的ACK时,拥塞窗口增加1。

当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。

快速恢复算法如下:

  • cwnd = sshthresh + 3,这里加3表示收到了三个重复ACK

  • 重传三个ACK指定的数据包

  • 每收到一个重复ACK,cwnd增加1

  • 如果收到了新数据的ACK,cwnd = sshthresh,然后进入拥塞避免算法 首先,发生快速重传,进入快速恢复算法,此时肯定需要降低cwnd来减缓拥塞,所以此时cwnd = cwnd/2 + 3。

    其次,cwnd逐渐加1,是为了尽快将丢失的数据包发出去,所以这里cwnd是增大的。

五、TCP优化

正确有效的使用TCP参数可以提高 TCP 性能。以下将从三个角度来阐述提升 TCP 的策略,分别是:

策略 TCP内核参数
SYN报文重传次数 tcp_syn_retries
SYN半连接队列长度 tcp_max_syn_backlog、somaxconn、backlog
SYN+ACK报文重传次数 tcp_synack_retries
accpet队列长度 min(backlog, somaxconn)

六、TCP 和 UDP 可以同时绑定相同的端口吗

答案是可以的,在计算机网络中,每一层都有其特定的 地址标识符,用于正确地将数据传递到目的地。

数据链路层MAC地址用于在局域网中寻找物理设备,因为MAC地址是唯一的,并且烧录在网卡的硬件中。

网络层IP地址用于在全球互联网中寻找设备。IP地址可以是动态分配的,也可以是静态配置的,它们标识了网络中的设备位置。

传输层使用端口号来区分同一个主机上的不同服务或应用程序。当一个数据包到达主机时,操作系统的网络栈会查看IP包头中的协议号字段,以确定数据应该由TCP模块还是UDP模块处理。协议号字段指定了传输层协议的类型,例如 TCP的协议号是6,UDP的协议号是17。

一旦确定了是 TCP 还是 UDP,数据包就会根据其端口号被进一步分发到正确的应用程序。端口号是一个16位的数字,范围从0到65535,其中0到1023是知名端口号,通常用于特定的服务,例如HTTP服务通常使用TCP端口80,DNS服务通常使用UDP端口53。其它端口号可以由操作系统动态分配给应用程序,或者由应用程序在运行时指定。

TCP/UDP 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。

示例: 假设有一台服务器同时提供DNS服务,DNS服务既支持使用TCP协议(用于处理较大的请求),也支持使用UDP协议(用于处理较小的请求)。DNS服务通常使用端口号53。

DNS查询:

  • 当在计算机上访问一个网址时,计算机需要将域名解析为IP地址。它会发送一个DNS查询请求。
  • 如果查询是一个较小的请求,计算机可能会使用UDP协议发送请求到DNS服务器的53端口。
  • 如果查询是一个较大的请求,计算机可能会使用TCP协议发送请求到同一个DNS服务器的53端口。

数据包传输:

  • 对于UDP请求,数据包包含源IP地址、源端口号(操作系统分配的临时端口号)、目的IP地址和目的端口号(53)。
  • 对于TCP请求,数据包也包含相同的源IP地址、源端口号、目的IP地址,但是目的端口号也是53。

服务器接收数据包:

  • DNS服务器收到数据包后,会检查IP包头中的协议号。
  • 如果协议号是17(UDP),服务器知道这是一个UDP数据包,并将其交给UDP模块处理。UDP模块根据目的端口号53将数据包转发给DNS服务应用程序。
  • 如果协议号是6(TCP),服务器知道这是一个TCP数据包,并将其交给TCP模块处理。TCP模块根据目的端口号53将数据包转发给DNS服务应用程序。

服务器响应: *` 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示主动向服务端发起连接,该报文不包含应用层数据,发送报文之后客户端处于 SYN-SENT 状态。

  • 第二次握手(SYN+ACK):服务器收到SYN报文后,确认客户端的连接请求有效,回复一个带有SYN和ACK(确认应答)标志的报文段。该报文中包含一个新的初始序列号Y以及确认号(Acknowledgment Number),确认号为X+1,表示已收到并确认客户端的SYN报文。服务器进入SYN-RCVD状态,等待客户端的最终确认。

  • 第三次握手(ACK):客户端收到服务器的SYN+ACK报文后,发送一个ACK报文段给服务器,确认号为Y+1,表示已收到并确认服务器的SYN报文。至此,双方都已确认对方的接收与发送能力,且初始序列号已协商一致,连接建立完成,客户端与服务器进入ESTABLISHED状态,可以开始双向的数据传输。