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

传输层————TCP

1.背景知识

TCP全称为 "传输控制协议(Transmission Control Protocol")。人如其名,要对数据的传输进行一个详细的控制。

在TCP通信中,内核会维护自己的发送缓冲区和接收缓冲区。应用层在调用  write 、 read 、 recv 、 sendto  等函数时,并不是直接将数据发送到网络或从网络接收数据,而是在用户级缓冲区和内核缓冲区之间进行数据拷贝。
当调用发送类函数(如  write 、 sendto )时,数据只是从应用层缓冲区复制到TCP的发送缓冲区,至于何时发送、发送多少数据、以及如何处理发送错误,完全由TCP协议栈自主决定。

1.1 应用层的主要职责

  1. 从内核接收缓冲区读取数据
  2. 对数据进行协议处理(报文分离、反序列化等)
  3. 将处理结果写入内核缓冲区

本质上,这些I/O函数只是数据搬运工,真正的网络传输决策由底层协议负责。

TCP通信中的发送和接收缓冲区机制类似于文件IO,只是IO设备从磁盘换成了网卡。应用层调用 write 、 read 、 recv 、 sendto 等函数时,只是在用户缓冲区和内核缓冲区之间拷贝数据,实际的网络发送时机、数据量和错误处理由TCP协议栈决定,应用层只需关注数据的协议处理和结果写入内核。

1.2 TCP缓冲区与阻塞I/O交互机制

无论是服务端还是客户端,数据发送时都是先从用户缓冲区拷贝到内核的发送缓冲区,再由TCP协议栈负责传输到对方的接收缓冲区,这个过程本质上也是拷贝操作。 read 函数的作用就是从接收缓冲区读取数据,然后交给应用层处理。如果接收缓冲区中没有数据, read 会进入阻塞状态,操作系统会将该进程挂起(状态设为S或T)并放入等待队列。一旦接收缓冲区有数据到达,操作系统会唤醒该进程,使其重新被调度执行,这就是I/O就绪的基本原理。

TCP/UDP之所以支持全双工通信,是因为每个文件描述符都配备了独立的发送缓冲区和接收缓冲区,从而可以同时进行读写操作。

TCP/UDP 的全双工能力来源于每个文件描述符配套了两个独立的缓冲区——发送缓冲区和接收缓冲区,它们本质上是由操作系统管理的多个 4KB 内存块组成。在网络编程中,打开一个网络连接相当于获取一个文件描述符,底层对应一个 struct file 对象,只是它不再指向磁盘设备,而是指向网络设备,并包含了相应的操作方法表和两个缓冲区。这样设计使得上层接口无需改变,只需替换底层实现即可从文件 I/O 切换到网络 I/O。这也解释了传输控制协议中“控制”二字的含义:应用层通过 write 或 send 将数据拷贝到发送缓冲区后,具体何时发送、发送多少数据、以及如何处理错误,都由 TCP 协议栈自主控制,而非应用程序直接决定。

UDP之所以缺乏“控制”能力,是因为它没有像TCP那样的发送缓冲区,一旦调用发送函数,数据就立即交给UDP协议栈处理,不再有后续的发送时机和流量控制机制。

2.TCP协议段格式

前20个字节是标准报头,数据部分就是有效载荷。

2.1 16位源端口

标识发送方的应用程序端口,用于在接收端区分不同的发送应用。

2.2 16位目的端口

标识接收方的应用程序端口,用于在接收端将数据分发到对应的应用。

2.3 4位首部长度

该字段以4字节(32位)为单位,标识TCP首部从开始到有效载荷(数据部分)的总长度。它的存在是为了让接收方能够准确区分开TCP首部和后续的应用层数据,尤其是在TCP首部包含“选项”字段时(因为选项长度不固定)。

  • 计算方式:TCP首部长度 =  4位首部长度的值 × 4字节(表示长度范围0-60,标准长度20,选项占40个字节)
    • 若该字段值为 5 ,则TCP首部长度为  5 × 4 = 20  字节(这是无选项时的默认最小长度)。
    • 若包含选项,例如值为 6 ,则首部长度为  6 × 4 = 24  字节,多出的4字节用于承载选项内容。

2.4 16 位窗口大小

2.4.1 基本定义与核心作用

  • 定义:TCP 报头中的 16 位窗口大小字段,用于告知通信对方(发送方)自己(接收方)当前接收缓冲区的剩余空间大小。
  • 核心作用:实现流量控制,通过明确告知发送方可发送的数据量上限,限制发送方的发送速率,避免因发送过快导致接收方缓冲区溢出。

2.4.2 关联的 TCP 通信特性

1.报文传输规范:客户端与服务器基于 TCP 协议互发消息时,每次发送的都是完整的 TCP 报文,且每个报文必定携带完整的 TCP 报头(16 位窗口大小字段包含在报头中)。

2.确认应答机制(可靠性基础)

  • 无论消息内容如何(包括空消息),接收方必须对收到的消息进行确认应答(ACK)。
  • 具体表现:客户端向服务器发消息→服务器必须应答;服务器向客户端发消息→客户端操作系统必须应答。

2.4.3 发送速率控制的依据

发送方的发送速度严格由对方(接收方)的接收缓冲区剩余空间大小决定,而该大小正是通过 16 位窗口大小字段传递的。

2.4.4 流量控制工作机制(核心逻辑)

1.接收方主动告知:接收方在发送 ACK 确认报文时,会将自己当前接收缓冲区的剩余空间数值填入 “16 位窗口大小” 字段,以此明确告知发送方 “当前最多可以再发送多少字节的数据”。

2.发送方动态调整:

  • 发送方根据收到的窗口大小数值,实时调整自身的发送速率和未确认数据量(确保未确认数据总量不超过对方告知的窗口大小)。
  • 特殊情况:若窗口大小为 0,发送方会立即停止发送数据,直到接收方通过后续 ACK 报文告知窗口重新打开(窗口大小 > 0)后,才恢复发送。

世界上不存在百分之百可靠的网络协议。最新的消息没有应答,所以无法保证发出的消息百分之百可靠。

2.5 32位序号

2.5.1 基本概念

  • 作用:对 TCP 报文段的数据部分进行编号,确保数据按序到达
  • 本质:给每个字节的数据分配唯一编号,便于接收方排序和去重

2.5.2 背景:为什么需要序号

  • 乱序问题:发送顺序是 1、2、3,收到可能是 1、3、2
  • 常见原因:
    • 网络路径不同
    • 延迟抖动
    • 重传机制
  • 影响:会严重影响视频、语音等实时应用的播放质量
  • 解决方案:TCP 自动重排,保证应用层收到有序数据

2.5.3 工作原理

  1. TCP 将用户层缓冲区数据按字节复制到 TCP 发送缓冲区
  2. 每个字节都有一个序号(类似数组下标)
  3. 数据分成多个数据段分批传输
  4. 每批数据段通常用最后一个字节的序号作为标识
  5. 接收方根据序号对数据段排序,确保按原始顺序交付应用层

2.5.4 序号规则

  • 初始序号 (ISN):由系统随机生成
  • 序号递增:后续序号 = 上一序号 + 数据字节数
  • 连续性:即使分批发送,序号也保持连续不间断

2.6 32位确认序号

2.6.1 基本定义

  • 作用:表示期望接收的下一个报文段的序号,用于确认已收到之前的所有数据
  • 本质:TCP 可靠性机制的核心组成部分

2.6.2 背景

  • 在同一时间段发送多条请求时,如何确保响应与请求正确对应?
  • 确认序号机制解决了这个问题,使接收方能够明确告知发送方哪些数据已收到

2.6.3 确认序号的填充规则

  • 计算公式:收到的报文序号 + 1
  • 表示接收方已成功接收该序号及之前的所有数据

2.6 4 工作机制与意义

  1. 确认范围:确认序号之前的所有数据都已收到
  2. 发送指引:下一次发送将从确认序号开始
  3. 容错能力:允许少量数据丢失,只需重传未确认部分

简单示例

  • 发送方发送:序号 1、2、3、4 的数据
  • 接收方收到:序号 1、2、3 的数据
  • 接收方回应:确认序号 4
  • 含义:表示 4 之前的数据都已收到
  • 发送方后续:从序号 4 开始继续发送,无需重传已确认部分

2.7 6个控制位/标记位

背景:在 TCP 通信场景中,服务器需同时处理多个客户端的请求,而 TCP 通信存在明确的流程:需先建立连接,再通过发送 TCP 报文开展正常数据通信,待通信完成后还需断开连接。这就导致服务器收到的 TCP 报文本身存在不同类型 —— 部分报文用于建立连接,部分用于向服务器传输数据,还有部分用于实现断开连接功能,且不同类型的报文对应着服务器需执行的不同动作。为了让服务器能够准确识别每一个 TCP 报文的类型,从而正确执行对应操作,TCP 报头中专门设置了 6 个标记位。

存在的意义:区分TCP报文类型。

2.7.1 ACK

确认序号是否有效,只要TCP三次握手成功,ACK标志位就会被默认置1(以此体现报文的应答属性),而ACK标志位不置1的情况仅出现在三次握手中的第一次。

2.7.2 SYN

又称为同步报文,表示请求建立连接。发送方发起的报文类型有多种,如果标记位设置为SYN代表的对方打算进行3次握手,也就是建立连接的请求。

2.7.3 FIN

通知对方,代表打算断开连接

注意:以上3个标记位不以传送数据为目的,而是通知接收方打算进行非传送数据的行为,也就是控制需求。

2.7.4 PSH

1.基本概念

  • PSH(Push):提示接收端应用程序立刻从 TCP 缓冲区读取数据
  • 作用:减少数据在缓冲区的滞留时间,加快数据交付

2.工作机制

  • 发送方机制
    • 定期询问接收方缓冲区剩余空间
    • 只要发送数据,接收方就需应答
    • 若接收方缓冲区大小长时间不更新,TCP 会发送PSH=1的报文
  • 接收方机制
    • 上层应用取走数据后,缓冲区空间更新
    • 会发送包含缓冲区大小更新的应答
    • 收到 PSH=1 的报文时,立即将缓冲区数据交付给上层

作为网络服务器,应该将接收缓冲区中的数据尽快读走,这意味着可以给对方通告一个更大的缓冲区接受窗口——接收缓冲区的剩余大小,这也意味着对方一次可以发送更多的数据。

2.7.5 RST

1.基本概念

  • RST:Reset,复位报文段
  • 作用:通知对方连接异常,需要重新建立连接

2.连接的本质

  • 双方操作系统创建连接结构体维护会话
  • 结构体包含:
    • 连接属性(起始序号、确认序号)
    • 建立时间
    • 客户端 IP 和端口
    • 发送 / 接收缓冲区信息
    • 连接状态及指针
  • 服务端通过链表管理所有连接(增删查改)

3.连接维护成本

  • 三次握手成功后创建结构体
  • 需要内存空间和 CPU 资源
  • 频繁创建 / 销毁会增加系统负担

4.工作机制

  • 当连接建立过程异常时如:发送方认为连接已建立,接收方未建立
  • 接收方会发送RST=1的报文
  • 要求对方重新建立连接

5.应用场景

注意,TCP虽然保证可靠性,但是允许连接建立失败。

2.7.6 URG

1.基本概念

  • URG:Urgent Pointer,紧急指针标记位
  • 作用:指示报文中是否包含需要优先处理的数据

2.工作机制

  • URG=0:紧急指针无效,无需特殊处理
  • URG=1:表示报文包含紧急数据,16 位紧急指针有效
  • 接收方应优先处理标记为紧急的数据

2.8 16位紧急指针

1.基本概念

  • 定义:当 URG 标志位为 1 时,表示紧急数据在报文内的位置
  • 计算方式:从当前 TCP 报文数据起始位置到 "紧急数据最后一个字节的下一个位置" 的相对偏移量

举例说明:假设当前TCP报文数据起始部分有一段紧急数据,紧急指针为500,那么紧急数据是从报文数据起始位置到第499字节(共500字节),从第500字节开始则为非紧急数据。

2.应用层体现

  • 发送紧急数据:
    • send函数的flags参数中传入MSG_OOB选项
    • 发送的就是 TCP 带外数据(紧急数据)
  • 接收紧急数据:
    • recv函数的flags参数中传入MSG_OOB选项
    • 可读取带外数据(紧急数据)

紧急指针默认只携带 1 个字节的紧急数据

3.应用场景

假如机房中有一个硬件服务器,该硬件服务器搭载了一种比较消耗资源的服务A,该服务器底层采用TCP协议。突然有一个客户端通过机房向服务A发起请求完成某种任务,可是使用过程中服务器突然没有应答了。

当客户端通过 ping 发现硬件服务器响应慢甚至无响应时,可能是服务器负载过高。为了及时了解情况并优化通信,可以在服务端实现紧急数据(带外数据)处理机制:正常通信的数据属于“带内数据”,需按顺序排队处理;而紧急数据(URG标志位+1字节数据)则可被服务端专门的程序优先读取,无需等待队列。

当客户端检测到服务器可能卡顿(或一直未响应)时,会立即发送一条带紧急数据的TCP报文,其中包含固定的状态查询编号。服务端收到后,会立即将对应软件的运行状态以紧急数据形式返回给客户端。这样,客户端就能快速得知服务器当前的工作状态,而不必等待正常数据流的响应,从而显著提升异常情况下的通信效率。

3.确认应答机制(ACK)

TCP将每个字节的数据都进行了编号,即为序列号。

每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发。

4.超时重传机制

4.1 情况1:数据丢失

  • 主机 A 发送数据给 B 时,可能因网络拥堵等原因导致数据无法到达主机 B
  • 主机 A 在特定时间间隔内未收到 B 的确认应答,就会进行重发

注意:如果发送后没有收到应答,主机 A 对发出的报文状态是完全未知的

4.2 情况2:应答丢失

  • 主机 A 未收到 B 的确认应答,也可能是因为 ACK 丢失
  • 这种情况下,主机 B 会收到很多重复数据
  • TCP 协议需要能识别重复包并丢弃,这是通过序列号实现的

4.3 结论

4.3.1 结论 1

  • 主机无法直接判断发出的报文是否丢失
  • 必须通过规定的时间间隔来决定是否重传
  • 这个时间间隔是 TCP 超时重传机制的核心

4.3.2 结论 2:超时时间设置

背景

  • 理想情况:找到最小时间,保证确认应答一定能在这个时间内返回
  • 实际情况:网络环境不同,这个时间差异很大
  • 超时时间太长:影响整体重传效率
  • 超时时间太短:可能导致频繁发送重复包

解决办法

  • TCP 会动态计算最优超时时间,与当前网络状况相关
  • Linux (BSD Unix 和 Windows 也类似) 实现特点:
    • 超时以 500ms 为基本单位
    • 每次判定超时重发的时间都是 500ms 的整数倍
    • 重传策略:
      • 第一次重传:等待 500ms
      • 第二次重传:等待 2×500ms
      • 第三次重传:等待 4×500ms
      • 以此类推,等待时间以指数形式递增
    • 当重传次数累计到一定值,TCP 认为网络或对端主机异常,强制关闭连接

5.连接管理机制

TCP通信是基于连接的,连接要经过三次握手建立连接,断开要经过四次挥手。

5.1 面试题

5.1.1 为什么要3次握手建立连接、4次回收断开连接?

在实际建立连接时,第二次握手并不是真的额外发送一个独立的报文,而是把“确认对方连接请求(ACK)”和“自己的连接请求(SYN)”合并成一个报文发送,也就是所谓的“捎带应答”。

这样一来,三次握手的中间两步就被压缩成了一次传输,既保证了双方的同步,又减少了通信开销。所以从某些方面来说,3次握手也可以被看成4次握手。

在 TCP 四次挥手中,第 2 次的 ACK 和第 3 次的 FIN 也可能被合并成一个报文发送,从而把四次挥手压缩成三次。从本质上讲,三次握手和四次挥手的核心都是通过“一来一回”的方式确保双方可靠通信,即客户端和服务端都能确认对方已经收到了自己的消息。因此,它们在逻辑上都是四次交互,只是由于“捎带应答”的优化,一个变成了三次握手,一个仍保持四次挥手。

教材中之所以把连接建立时的两个报文压缩成一次,而断开连接时不压缩,是因为连接建立时服务端在收到客户端 SYN 后必然会立即回应自己的 SYN,二者没有协商或等待的必要,因此可以无条件合并成 SYN+ACK;而断开连接时,服务端收到 FIN 后可能还有数据未发送完,需要先回复 ACK,待数据传输完毕后再发送 FIN,这两个动作之间存在不确定的时间间隔,能否合并成一个报文具有巧合性,所以大多数情况下保持四次挥手的形式。

5.1.2 建立连接为什么必须3次握手?

1.可靠验证全双工

建立连接之所以要三次握手,是因为可以通过客户端和服务端各自发送并确认SYN和ACK,验证双方在两个方向上都能独立地发送与接收数据,从而实现全双工通信的验证。

2.技术次握手可以确保一般情况握手失败的连接成本嫁接到客户端上。

在第一次握手阶段,如果客户端向服务器连续发送大量 SYN 请求,服务器必须逐一接收并为每个请求分配内存资源以维持连接状态,这极易导致 SYN 洪水攻击,使服务器资源耗尽而无法正常服务,因此这种未经限制的处理方式存在严重安全隐患,显然不可行。

如果采用两次握手,双方都会遵循“第一次握手后服务器先分配资源,第二次握手后客户端再分配资源”的规则。这样一来,服务器会在客户端尚未完全建立连接前就为请求分配内存并维持连接状态。此时若客户端在发送 SYN 后异常崩溃,服务器仍会在一段时间内保留该连接,直到超时才关闭。当服务端并发量较大时,即使只有少量客户端出现异常,也会导致服务器长时间挂着这些无效连接,浪费资源并影响性能。这就说明,仅靠两次握手在连接建立阶段存在明显漏洞,难以应对异常情况。

采用3次握手时,第1次或第2次握手报文丢失都会有相应的重传机制,双方能及时发现并处理,而且在握手未完成前,连接的资源成本还很低。最关键的是第3次握手的情况——如果第3次 ACK 丢失,客户端会认为连接已建立并维持相应资源,而服务器则因未收到确认而不会建立连接。这样一来,握手失败的成本主要由客户端承担,服务器在这种情况下几乎没有额外负担,从而保护了服务器的稳定性,即使面对大量客户端连接请求时也能保持可靠运行。

5.1.3 补充:肉鸡

指被恶意分子通过恶意邮件、诱导连接或下载等方式植入木马程序的客户端电脑。木马会让电脑在特定端口定期保持开放,与控制主机建立通信并领取攻击任务,例如在同一时间向目标服务器发起大量正常的TCP三次握手请求,耗尽服务器连接资源。TCP协议本身无法防御这类攻击,需要借助防火墙策略、流量预警和限流等额外安全措施来应对。

5.1.4 断开连接的本质

断开连接是双方的事情,其本质就是没有数据给对方发送了,数据是双方都可能发送的,所以必须断开2次。

6.TCP状态

6.1 ESTABLISHED

ESTABLISHED表示连接建立成功,连接是否建立成功和上层是否调用accept无关,这体现了3次握手是双方操作系统自动完成的!

6.2 全连接队列

6.2.1 操作系统管理服务端连接的核心机制

  1. 操作系统以队列管理服务端已建立的连接;​
  2. 三次握手的本质:形成一个连接,并将该连接列入队列;​
  3. accept 的作用:从队列中取出连接,与特定文件相关联,返回特定的文件描述符;​
  4. listen 函数:第二个参数 backlog + 1 表示底层已建立好的连接队列的最大长度,该队列被称作全连接队列;​
  5. 队列满时的处理:若队列满了仍有新连接建立,新连接不会入队列,此时服务器状态为 SYN_RCV。

面试题1:为什么全连接队列长度不能太长?

当服务器处理速度跟不上连接建立速度时,队列中会堆积大量已建立但未被处理的连接。这些连接虽然占用了系统资源(内存、文件描述符等),但暂时没有产生任何业务价值。如果队列过长,系统会在高负载时维持过多这类 "闲置" 连接,导致资源被无效占用,进一步加剧服务器负担,甚至引发系统性能下降或服务中断。

面试题2:为什么不能没有全连接队列?

全连接队列不能没有,因为它在 TCP 协议栈和应用层之间提供临时缓冲。三次握手完成后,连接已建立,但应用可能暂时无法立即 accept。没有这个队列,一旦应用繁忙就只能直接拒绝新连接,无法应对流量波动且无法充分利用服务器上层资源,系统处理突发请求的能力会大幅下降。合理的队列长度能平滑连接建立与应用处理之间的速度差异,保证服务稳定性。

6.3 SYN_RCV

6.3.1 SYN_RCV状态与半连接队列

  • 状态触发时机:在 TCP 三次握手过程中,服务器收到客户端的 SYN 请求,但尚未完成整个握手流程时,会将该连接置于 SYN_RCV 状态,此状态下的连接被称为半连接。
  • 管理方式:服务器通过半连接队列来统一管理所有处于 SYN_RCV 状态的未完全建立连接。
  • 资源释放机制:半连接队列中的节点不会被长时间保留,超时后会自动释放,以避免系统资源浪费。

6.3.2 SYN洪水攻击

当攻击者大量发送 SYN 请求而不回应服务器的 SYN+ACK 时,半连接队列会迅速被占满。一旦队列达到上限,服务器将无法再接收新的 SYN 请求,导致正常连接无法建立。这种利用 TCP 握手机制缺陷发起的攻击,就是典型的SYN 洪水攻击。

场景:这就像学校抢课系统,当大量学生同时访问时,处理请求的队列(包括半连接队列和全连接队列)可能全部占满,此时系统会显示"服务器繁忙"。但刷新几次后又能进入页面,是因为之前的请求超时释放了队列空间,新的请求才有机会被处理。

6.4 客户端和服务器连接不一致

正常 TCP 连接建立过程:

  1. 客户端发送 SYN,进入 SYN_SENT 状态
  2. 服务器收到 SYN,回复 SYN+ACK,进入 SYN_RECEIVED 状态
  3. 客户端收到 SYN+ACK,回复 ACK,进入 ESTABLISHED
  4. 服务器收到 ACK,也进入 ESTABLISHED

不一致的典型情况:

  1. 客户端已发 SYN,但服务器未收到 → 客户端在 SYN_SENT 超时重传,服务器仍在 LISTEN
  2. 服务器回复 SYN+ACK,但客户端未收到 → 服务器在 SYN_RECEIVED 等待 ACK,客户端在 SYN_SENT 重传
  3. 客户端已 ESTABLISHED,但服务器未收到 ACK → 客户端认为连接已建立,服务器仍在 SYN_RECEIVED
  4. 服务器提前关闭连接 → 客户端在 ESTABLISHED,服务器却进入 CLOSED 或 LISTEN

6.5 TIME_WAIT

6.5.1 基本概念

  • 主动断开连接的一方在四次挥手完成后进入 TIME_WAIT 状态
  • 等待一段时间后才会自动释放到 CLOSED 状态
  • 处于 TIME_WAIT 时,对应 IP 和端口仍被占用

6.5.2 服务端重启问题

  • 如果服务端主动断开连接并进入 TIME_WAIT 状态
  • 此时立即重启服务会因端口占用而失败
  • 解决方法:使用 setsockopt 设置 SO_REUSEADDR 属性
  • 允许地址复用,新进程可立即绑定相同 IP 和端口

6.5.3 TIME_WAIT 等待时长

  • TIME_WAIT 等待时长
  • 等待时间 = 2 × MSL(Maximum Segment Lifetime)
  • MSL:报文在网络中的最大存活时间
  • RFC1122 规定 MSL 为 2 分钟
  • CentOS7 默认配置为 60 秒
  • 查看:cat /proc/sys/net/ipv4/tcp_fin_timeout
  • 修改:echo [时长] > /proc/sys/net/ipv4/tcp_fin_timeout

6.5.4 TIME_WAIT 的主要目的

  1. 历史数据消散:确保网络中残留的旧数据被丢弃
  2. 容错机制:保证最后一个 ACK 报文能被对方接收

7.流量控制

接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)。

7.1 初始数据量控制

  • 前两次握手已协商双方的接收能力
  • 第一次发送时根据协商结果确定合理数据量

7.2 三次握手与数据传输

第 3 次握手可以携带数据,本质上属于 "捎带应答" 机制。

7.3 流量控制的作用

  • 属于可靠性机制,防止正常丢包
  • 间接提高效率:减少重传需求
  • 接收端通过 TCP 首部的 16 位窗口字段告知发送端窗口大小

7.4 窗口大小限制与扩展

接收端如何把窗口大小告诉发送端呢?

回忆我们的TCP首部中,有一个16位窗口字段,这里存放了窗口大小信息。
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移 M 位。

7.5 滑动窗口

在确认应答策略中,发送方每发送一个数据段都必须等待接收方返回ACK确认后才能继续发送下一个。虽然实现简单且可靠,但在网络延迟较高或传输距离较远的情况下,发送方大部分时间处于等待状态,而非持续传输数据,导致网络利用率显著降低。此外,TCP还需缓存已发送但尚未收到应答的报文以备重传。这些问题直接凸显了引入滑动窗口机制的必要性。

7.5.1 发送缓冲区区域划分

  • 已发送已确认:可被用户输入内容覆盖(从 TCP 缓冲区移除)
  • 已发送未确认:“滑动窗口” 区域,是可发送 / 已发送但未收到应答的部分,大小不超过对方接收缓冲区剩余空间。
  • 待发送

7.5.2 滑动窗口的本质与移动逻辑

通过 start(窗口起始指针)和 end(窗口结束指针)的动态调整,改变可发送数据的范围:

  • 收到 ACK 时,start 右移对应确认的字节数
  • 接收方可用接收区增大时,end 右移,允许发送更多数据

7.5.3 丢包场景下的滑动窗口与重传机制

情况一:ACK 丢失但数据已到达

如果2001-3000的报文已被接收方收到,但返回的ACK在传输中丢失,而1001-2000、3001-5000的ACK均正常返回,接收方会根据“确认序号为x表示x之前所有报文已全部收到”的规则,直接确认到5001。此时滑动窗口整体右移,最左端直接跳到5001,即使部分ACK丢失,也不会影响数据传输流程的推进,因为后续ACK可以进行确认。此时,主机A会进入超时等待时间,未来将对2001-3000补发。

情况二:数据直接丢失

  • 若 1001-2000 报文丢失,其他报文到达
  • 接收方返回确认序号 1001
  • 主机 A 收到 3 个相同的重复确认后,触发快重传,立即重发 1001-2000 报文
  • 补发成功后,确认序号更新到 7001,滑动窗口移到 7001

7.5.4 快传和重传的区别

已经有了快重传为什么还有超时重传?

机制触发条件优势 / 适用场景失效场景
快重传收到 3 个相同的重复确认减少等待时间,提高传输效率丢失窗口内第一个报文、网络大面积拥塞 / 丢包、ACK 连续丢失
超时重传报文发送后超时未收到确认复杂网络状况下的最后保障效率较低

7.5.5 滑动窗口的移动与大小变化

1.移动方向

仅向右移动,不会向左移动(左指针仅递增)

2.移动与大小变化

移动情况窗口大小变化说明
左端移动、右端不变缩小部分数据被确认
左端和右端同时右移不变确认旧数据,发送新数据
右端右移、左端不变扩大接收方通告更大可用缓冲区

3.窗口大小规律

  • 变大:接收方窗口增大
  • 变小:已发送数据被确认,左端右移但右端未动
  • 不变:确认数据量与新允许发送数据量相等

注意:窗口大小不能超过接收方的可用缓冲区大小。

4.窗口为 0 的情况

  • 如果接收方的接收窗口为 0,表示接收方暂时无法接收新数据。
  • 此时发送方必须停止发送(除了可能的探测报文),直到接收方再次通告一个大于 0 的窗口。

用计算机的语言表述上述概念:

// 窗口边界定义
int start;  // 已确认序号的下一个序号,即窗口最左端
int end;    // 窗口最右端 = start + 窗口大小// 流量控制的实现思路
// 接收方根据自己的缓冲区情况动态调整窗口大小并告知发送方
// 发送方只发送窗口内的数据// 实际窗口大小计算
int window_size = min(receiver_window, valid_data, congestion_window);
// 注:congestion_window(拥塞窗口)后续讲解

注意:缓冲区实际上可以分为四部分

已发送已确认、已发送未确认、待发送,以及一部分未填写数据的区域。其中,未填写数据的部分在逻辑上可以归到已确认的类别中。

7.5.6 滑动窗口的越界处理

采用环状算法避免发送缓冲区越界。

7.5.7 序号协商的作用

  • 解决断开重连后旧延迟数据包的干扰问题
  • 新连接建立时,双方交换最新序列号(上次结束序号 + 1)并协商起始序号
  • 接收方丢弃序号低于协商起点的数据包

8.延迟应答

8.1 背景

在TCP通信中,发送方一次性发送更多数据通常能提高传输效率,而这主要受限于接收方通告的窗口大小(TCP报头中的Window字段)。要让接收方通告更大的窗口,关键在于接收方尽快释放其内核接收缓冲区。

8.2 原理

接收方可以通过延迟应答(收到数据后不立即回复,而是等待一段时间,让上层应用有更多时间取走数据)来尝试扩大通告窗口,但这种方法并非总能提高效率,因为它只是在"赌"上层会及时处理数据。

作为用户更可靠的做法是:应用层通过read/recv尽快将数据从内核缓冲区取走,这样接收方就能在确认报文里通告更大的窗口,从而让发送方可以一次传输更多数据,真正提升通信效率。

8.3 TCP延迟应答的策略

  • 数量限制: 每隔N个包就应答一次。
  • 时间限制: 超过最大延迟时间就应答一次。

9.捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的,这意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you"。那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine, thank you" 一起回给客户端。

10.拥塞控制

我们已经知道,3次握手实际上每次要做的是建立连接→协商起始序号→协商双方的接收缓冲区大小。几乎所有的策略,都是在两端的机器上起作用的!但是网络中的数据包大部分时间都是在网上的,不光要考虑要考虑两端的机器需要用到的策略,也要对双方网络通信时的网络信道有所评估。所以TCP也引入了拥塞控制。

10.1 数据发送问题分析

  • 少量丢包:通常是正常现象
  • 大量丢包:可能是网络问题(硬件故障或数据量过大导致阻塞)

10.2 TCP 对拥塞的判断

当通信双方出现大量丢包,TCP 会判断网络拥塞。此时发送方应:

  • 避免立即超时重发(无效或加重拥塞)
  • 减少发送数据量(网络资源共享,TCP 协议实现多主机 "共识")

10.3 慢启动机制

TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。

10.3.1 拥塞窗口 (cwnd)

此处引入一个概念程为拥塞窗口:

  • 初始值为 1
  • 每收到一个 ACK 应答,拥塞窗口加 1
  • 实际发送窗口 = min (接收方窗口,有效数据,拥塞窗口)

滑动窗口协议(尤其是 TCP)里发送方真正能一次发出去的数据量由三个限制因素共同决定:

  • 接收方窗口:接收方告诉发送方当前的缓冲区还能接收多少字节。
  • 有效数据:指发送方应用层还待发送的数据量。
  • 拥塞窗口:发送方自己根据网络拥塞情况算出来的一个限制值。

10.3.2 拥塞窗口增长策略

拥塞窗口:主机判断网络健康程度的指标,如果超过拥塞窗口会引发网络拥塞。网络是动态的,拥塞窗口本身肯定不能是静态的。

像上面这样的拥塞窗口增长速度是指数级别的,"慢启动" 只是指初使时慢,但是增长速度非常快。

为了不增长的那么快,因此不能使拥塞窗口单纯的加倍,此处引入一个叫做慢启动的阈值。当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长。

慢启动的阈值:最近一次发生网络拥塞时,拥塞窗口大小/2。

11.面向字节流

11.1 基本定义

TCP 是面向字节流的传输层协议,它将上层应用数据视为连续的字节序列,不关心具体的报文格式。

11.2 读写特性

由于缓冲区的存在,TCP 的读写操作不需要一一匹配:

  • 写操作:发送 100 字节可一次完成,也可分 100 次每次写 1 字节
  • 读操作:接收 100 字节可一次读取,也可分多次读取,与发送方式无关

11.3 缓冲区工作机制

  • 客户端:发送数据时,会先将数据从用户层缓冲区拷贝到内核层缓冲区,无论这个过程中数据被拷贝多少次,最终都会以字节流的形式呈现。TCP会根据网络状况动态评估每次实际发送多少字节。
  • 服务端:从内核层接收缓冲区读取数据时,读取多少字节完全由用户决定。

无论上层应用发送了多少个请求,在TCP层面都只是连续的字节数据。TCP不关心上层协议的报文格式,它只处理字节流,这体现了TCP作为传输层协议的面向字节流的特性。

11.4 应用层注意事项

  • TCP 不保证应用层报文边界
  • 一次 send/write 只是将数据拷入内核缓冲区,实际发送和接收方式由操作系统和协议栈决定。
  • 必须在应用层实现:长度标识、分隔符、其他帧同步机制

12.粘包问题

12.1 粘包问题的产生

  • 发送端数据到达接收端缓冲区后,用户需通过读取操作将数据拷到用户空间
  • 操作系统只负责字节流的传输与缓冲,不处理序列化 / 反序列化
  • 若没有明确的边界标识,读取操作可能得到:
    • 不完整的报文
    • 多个报文合并在一起
  • 这种现象称为粘包问题(用户层概念)

12.2 解决粘包问题的方法

必须在应用层通过协议明确报文边界:

1.定长报文

  • 每个报文固定长度
  • 接收方按固定长度读取

2.使用特殊字符

  • 用特定字符作为报文结束标志
  • 接收方读到该字符即认为一个报文结束

3.自描述字段 + 定长报头

  • 报头包含报文长度信息
  • 先读取报头,再按指定长度读取报文内容

4.自描述字段 + 特殊字符

  • 结合长度信息和特殊字符双重标识
  • 提高边界识别的可靠性

13.TCP异常情况

13.1 进程终止

连接和进程没有直接关系,而是和文件直接相关。文件的生命周期是随进程的。每一次用户发起连接,3次握手是由OS自动完成。所以一个进程终止,对于连接来说都只认为是进程正常结束,所以连接会正常进行4次挥手。

13.2 机器重启

当我们关机时,机器会先杀掉所有的进程,所有进程建立过的连接也要通过4次挥手把连接断开,然后再今天退出。或者直接杀掉进程,让操作系统自己回收。

13.3 机器掉电/网线断开

当机器掉电或网线断开时,TCP 连接的处理方式有所不同:

  • 如果是机器掉电,客户端操作系统立即停止运行,没有机会执行 TCP 的四次挥手流程,服务器会一直维护该连接直到保活定时器超时才断开。
  • 如果是网线断开,浏览器会检测到网络状态变化并主动关闭连接。再次连接网线后,客户端的连接状态已丢失,此时发送数据会导致服务器与客户端的连接认知不一致,服务器会发送 RESET 让客户端重新建立连接。
  • 对于服务器来说,TCP 保活定时器会定期发送保活探测包,如果多次无应答,服务器会立即意识到连接异常并主动断开。

14.面试题:用UDP实现可靠传输

参考TCP的可靠性机制,在应用层实现类似的逻辑,例如:

  • 引入序列号,保证数据顺序;
  • 引入确认应答, 确保对端收到了数据;
  • 引入超时重传,如果隔一段时间没有应答, 就重发数据。
  •  ……

15.文件和socket的关系

服务器在运行过程中会收到多个报文,这些报文需要通过“先描述、再组织”的方式进行管理。每个报文在网络中的数据结构由 struct sk_buff_head  表示,并以双链表的形式组织起来。该结构体中包含锁机制,这是因为在 TCP 协议中存在接收缓冲区和发送缓冲区,而报文的接收与发送本质上构成了一个生产者-消费者模型,需要通过锁来确保数据操作的线程安全。

在 Linux 网络栈中, struct sk_buff (skb)是核心数据结构,它通过指针(如 head 、 data 、 tail 、 end )管理一块连续内存缓冲区;封装时,协议层通过 skb_push()  或 skb_reserve()  调整 data  指针向左移动,在数据前面插入协议头,而无需复制数据内容;解包时,则通过 skb_pull()  或直接移动 data  指针跳过头部,让后续处理直接访问有效载荷;整个过程本质上就是利用指针操作在同一块内存中调整数据视图,从而高效完成协议数据单元的构建与解析。

http://www.dtcms.com/a/393937.html

相关文章:

  • [已更新]2025华为杯B题数学建模研赛B题研究生数学建模思路代码文章成品:无线通信系统链路速率建模
  • 机器学习相关内容
  • 【win11】自动登录,开机进入桌面
  • 关系型数据库系统概述:MySQL与PostgreSQL
  • python编程练习(Day8)
  • 【Linux命令从入门到精通系列指南】apt 命令详解:Debian/Ubuntu 系统包管理的现代利器
  • xtuoj 7的倍数
  • 【开题答辩全过程】以 java牙科门诊管理系统为例,包含答辩的问题和答案
  • 【论文速递】2025年第19周(May-04-10)(Robotics/Embodied AI/LLM)
  • 鸿蒙 - 验证码功能
  • 大数据毕业设计选题推荐-基于大数据的汽车之家数据分析系统-Hadoop-Spark-数据可视化-BigData
  • Bioconductor 项目为高通量生物数据分析提供了大量强大的工具 Bioconductor规范,核心是一系列设计精良、标准化的数据对象
  • 还有新援?利物浦即将启动预签协议,锁定英格兰新星
  • Audacity音频软件介绍和使用
  • SpringBoot配置优化:Tomcat+数据库+缓存+日志全场景教程
  • 《数据库系统概论》——陈红、卢卫-1-数据库系统概述
  • VLA-Adapter:一种适用于微型 VLA 的有效范式
  • JVM内存模型深度剖析与优化
  • 固定收益理论(六)波动率曲面、曲线及其构建模型
  • Zotero使用学习笔记
  • 分布式 | 布隆过滤器实战指南:原理、编码实现、应用与Redisson最佳实践
  • STM32的VSCode下开发环境搭建
  • Rsync+sersync实现数据实时同步
  • HttpServletRequest/Response/请求转发/响应重定向
  • 数据结构(2) —— 双向链表、循环链表与内核链表
  • 告别传统打版:用CLO 3D联动Substance,打造超写实数字服装
  • Linux | i.MX6ULL Sqlite3 移植和使用(第二十三章)
  • SpringBoot整合Smart Doc
  • 部署dataxweb
  • C#练习题——双向链表的创建,添加和删除