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

网络通信的奥秘:TCP与UDP详解(三)

TCP数据包详解

假设你写的纸条(TCP 数据包)长这样:

内容类型具体内容(类比 TCP 字段)占多少字节
首部(备注)源端口(67890)2
目的端口(443)2
序号(1000)4
确认序号(2001)4
首部长度(6)+ 保留(0)+ 控制位2
窗口大小(8192)2
检验和(0x1234)2
紧急指针(0)2
选项(MSS=1460)4
数据(正文)晚上一起吃火锅20
  • 这里的 “首部长度” 字段会填 “6”(注意:4 位首部长度的单位是 “4 字节”,所以 6×4=24 字节)—— 意思是 “整个备注(首部)一共 20 字节”。
  • 服务器收到后,就会跳过前 24 字节,从第 25 字节开始读,这才是 “晚上一起吃火锅” 这个核心数据,不会把 “源端口 67890”“序号 1000” 这些备注当成正文读。

为啥不能只算 “端口号 + 序号 + 确认序号”?

因为 TCP 首部可能有 “选项字段”(比如上面的 MSS=1460),这个字段不是每次都有,长度也不固定(可以是 0 字节,也可以是 40 字节)。如果只算 “端口号 + 序号 + 确认序号”(固定 10 字节),一旦加了选项(比如加 4 字节 MSS),首部总长度就变成 14 字节,服务器还按 “跳过 10 字节” 读,就会把选项的 4 字节当成数据的一部分,读成 “1460 晚上一起吃火锅”,完全乱了。

而 “首部长度” 字段算的是 “整个首部的总长度”(不管有没有选项,都按实际长度算),就能确保服务器永远找对数据的起始位置 —— 这就是它的关键作用。

首部总长度 = 必须有的 20 字节 + 实际加了的选项字节数,没加选项就只算 20 字节.

TCP 预留字段和选项字段的核心差异:

1. 预留字段(6 位):“固定死的小角落”,只能应付 “未来极小的改动”

  • 特点:长度固定(6 位,连 1 字节都不到),且 “必须永远留空”(现在全为 0,未来也只能填非常简单的标记)。
  • 为啥不能用来替代选项?就像你笔记本最后留的 6 页空白,只能写点 “勾、叉” 这种简单符号 —— 如果未来需要加一个 “协商最大传输速度”(需要多个字节),6 位根本不够用,强行写会挤掉其他内容(比如覆盖掉序号字段)。TCP 设计时就明确:预留字段是 “应对极端情况的应急空间”,比如未来发现某个致命漏洞,需要加一个 1 位的标记(比如 “是否加密”),就可以用这里的 1 位,其他 5 位继续留空。但它绝对承担不了 “灵活扩展功能” 的任务。

2. 选项字段:“可长可短的便利贴”,专门应付 “多样化的扩展需求”

  • 特点:长度不固定(可以是 0 字节,也可以到 40 字节),且 “按需添加”(比如只在建立连接时协商 “最大分段大小”,平时传输数据时不加)。
  • 为啥必须有选项字段?因为 TCP 需要支持的 “扩展功能” 太多样了,而且很多功能 “不是每次传输都需要”:
    • 比如 “MSS(最大分段大小)”:只在三次握手时协商一次(告诉对方 “我一次最多能接 1460 字节”),之后传输数据就不用带了 —— 如果用固定预留字段,总不能每次传数据都带着这个协商结果吧?太浪费空间。
    • 比如 “时间戳”:有些场景需要(比如计算网络延迟),有些场景不需要(比如简单的文字聊天),用选项字段可以 “需要时加上,不需要时去掉”,灵活控制首部长度。
    • 比如 “窗口扩大因子”:当接收方缓存很大(超过 65535 字节),需要扩展窗口大小时才用 —— 这种 “偶尔才需要的功能”,如果占用固定预留字段,就是对空间的浪费。

3. 为啥不直接 “加长 TCP 首部” 或 “加多预留字段”?

  • 加长首部 =“永远带着不必要的行李”:TCP 首部的默认长度是 20 字节(无选项时),如果为了扩展功能,强行把首部加长到 40 字节(比如多加 20 字节预留),那么每次传数据都要多传 20 字节的 “空内容”—— 就像你出门永远带着一沓用不上的便利贴,徒增负担(网络传输中,多余的字节会浪费带宽,降低效率)。而选项字段是 “按需添加”,平时传输数据时可以不加,首部保持 20 字节的紧凑 size,高效!

  • 加多预留字段 =“预测未来”,根本做不到:TCP 设计时(1981 年),谁也想不到几十年后会有 “大数据传输、高并发通信” 这些需求。如果当时盲目加多预留字段(比如加 100 位),很可能 99% 的位永远用不上,纯浪费;而如果预留少了,未来又不够用。选项字段的 “动态长度” 完美解决了这个问题 —— 未来有新需求,就定义一个新的选项(比如 2000 年需要 “时间戳防回绕”,就加一个时间戳选项),不用改 TCP 的基本结构,兼容性极强。

总结:预留字段是 “应急小仓库”,选项字段是 “灵活工具箱”

  • 预留字段:固定 6 位,像口袋里的 “备用硬币”,只能应付小额支付(简单标记),平时用不上,关键时刻救急。
  • 选项字段:长度可变,像随身带的 “多功能工具刀”,需要拧螺丝就装螺丝刀头,需要剪线就装剪刀头(按需扩展功能),不用时可以收起来,不占地方。

二者分工不同:预留字段应付 “极小、极特殊的未来改动”,选项字段负责 “多样化、可按需开启的扩展功能”—— 这正是 TCP 协议能从 1981 年用到现在,还能适应各种网络场景的关键设计之一。

TCP 是 “拆包保证有序、确认保证不丢、单连接保证串行” 的可靠传输

  • 拆包 + 序号:再长的消息也能拼回去;
  • 确认 + 重传:再差的网络也能保证不丢;
  • 单连接串行:再多的消息也能按顺序交付。

三次握手:

超时重传:

  • 主机A发送数据给B之后,可能因为⽹络拥堵等原因,数据⽆法到达主机B;
  • 如果主机A在⼀个特定时间间隔内没有收到B发来的确认应答,就会进⾏重发;
  • 但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;

因此主机B会收到很多重复数据.那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉. 这时候我们可以利⽤前⾯提到的序列号,就可以很容易做到去重的效果. 那么,如果超时的时间如何确定?

  • 最理想的情况下,找到⼀个最⼩的时间,保证"确认应答⼀定能在这个时间内返回".
  • 但是这个时间的⻓短,随着⽹络环境的不同,是有差异的.
  •  如果超时时间设的太⻓,会影响整体的重传效率;
  • 如果超时时间设的太短,有可能会频繁发送重复的包; TCP为了保证⽆论在任何环境下都能⽐较⾼性能的通信,因此会动态计算这个最⼤超时时间.
  •  Linux中(BSDUnix和Windows也是如此),超时以500ms为⼀个单位进⾏控制,每次判定超时重发的 超时时间都是500ms的整数倍.
  • 如果重发⼀次之后,仍然得不到应答,等待2*500ms后再进⾏重传.
  • 如果仍然得不到应答,等待4*500ms进⾏重传.依次类推,以指数形式递增.
  • 累计到⼀定的重传次数,TCP认为⽹络或者对端主机出现异常,强制关闭连接.

连接管理 

在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接

建⽴连接的意义:

  1. 投⽯问路,确认当前通信路径是否畅通.
  2. 协商参数,通信双⽅共同确认⼀些通信中的必备参数数值.

滑动窗⼝

窗⼝⼤⼩指的是⽆需等待确认应答⽽可以继续发送数据的最⼤值.上图的窗⼝⼤⼩就是4000个字节 (四个段).

  • 发送前四个段的时候,不需要等待任何ACK,直接发送;
  • 收到第⼀个ACK后,滑动窗⼝向后移动,继续发送第五个段的数据;依次类推;
  • 操作系统内核为了维护这个滑动窗⼝,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
  • 窗⼝越⼤,则⽹络的吞吐率就越⾼;

如果出现了丢包,如何进⾏重传?这⾥分两种情况讨论

数据包直接丢了:

阻塞窗口的确认与重传逻辑

当某一段数据(如 1001~2000)丢失时:

  • 接收方会持续发送 “期望下一个是 1001” 的确认,直到收到该段数据。
  • 发送方收到3 次重复确认后会快速重传该段;如果没收到重复确认,就会等超时时间,超时后也会重传。若多次重传仍失败,才会认为连接中断。

 窗口的滑动与后续传输

在等待 1001~2000 重传的过程中,滑动窗口会越过阻塞的段,继续传输后续未阻塞的窗口数据(如 4001 及之后的字节)。这一设计让 TCP 在保证可靠性的同时,最大限度地利用了网络带宽,避免因个别段丢失导致整体传输停滞。

这种机制被称为"⾼速重发控制"(也叫"快重传").

流量控制

接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继⽽引起丢包重传等等⼀系列连锁反应.

因此TCP⽀持根据接收端的处理能⼒,来决定发送端的发送速度.这个机制就叫做流量控制

  • 接收端将⾃⼰可以接收的缓冲区⼤⼩放⼊TCP⾸部中的"窗⼝⼤⼩"字段,通过ACK端通知发送端;
  • 窗⼝⼤⼩字段越⼤,说明⽹络的吞吐量越⾼;
  • 接收端⼀旦发现⾃⼰的缓冲区快满了,就会将窗⼝⼤⼩设置成⼀个更⼩的值通知给发送端;
  • 发送端接受到这个窗⼝之后,就会减慢⾃⼰的发送速度;
  • 如果接收端缓冲区满了,就会将窗⼝置为0;这时发送⽅不再发送数据,但是需要定期发送⼀个窗⼝探 测数据段,使接收端把窗⼝⼤⼩告诉发送端

拥塞窗⼝

  • 发送开始的时候,定义拥塞窗⼝⼤⼩为1;
  • 每次收到⼀个ACK应答,拥塞窗⼝加1;
  • 每次发送数据包的时候,将拥塞窗⼝和接收端主机反馈的窗⼝⼤⼩做⽐较,取较⼩的值作为实际发送的窗⼝; 像上⾯这样的拥塞窗⼝增⻓速度,是指数级别的."慢启动"只是指初使时慢,但是增⻓速度⾮常快.
  • 为了不增⻓的那么快,因此不能使拥塞窗⼝单纯的加倍.
  • 此处引⼊⼀个叫做慢启动的阈值
  •  当拥塞窗⼝超过这个阈值的时候,不再按照指数⽅式增⻓,⽽是按照线性⽅式增⻓
  • 当TCP开始启动的时候,慢启动阈值等于窗⼝最⼤值;
  • 在每次超时重发的时候,慢启动阈值会变成原来的⼀半,同时拥塞窗⼝置回1;

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对⽅,但是⼜要避免给⽹络造成太⼤压⼒的折中⽅案.

粘包问题

一、先明确:什么是粘包?

TCP 是 “面向字节流” 的协议,它会把应用程序发送的数据当成连续的字节流来处理,而不是按 “个”(比如每次 send 的数据包)来划分边界。当发送方连续发送多组数据,或接收方读取不及时时,接收方可能会一次性收到多组数据的拼接内容,这种 “多组数据被合并接收” 的现象,就是粘包

举个例子:

  • 发送方分 2 次 send:“hello” 和 “world”
  • 接收方可能 1 次 recv 就收到:“helloworld”(这就是粘包),而不是预期的 2 次分别收到。

二、粘包的 2 个核心原因

粘包的根源是 TCP 的 “流特性”+“缓冲区机制”,具体分发送方和接收方两类原因:

1. 发送方导致的粘包:Nagle 算法与缓冲区合并

TCP 默认开启Nagle 算法,其目的是减少网络小数据包数量(避免网络拥堵),核心逻辑是:

  • 发送方会先把应用程序发送的数据存入发送缓冲区
  • 如果缓冲区中的数据量较小,不会立即发送,而是等缓冲区满了、或等收到前一个数据包的确认后,再将缓冲区中的多组小数据 “合并成一个大数据包” 发送;
  • 这就导致发送方的多组小数据被 “粘” 成一个包发出去,接收方自然会粘包。
2. 接收方导致的粘包:接收缓冲区与读取时机

接收方也有接收缓冲区,数据到达后会先存入缓冲区,再由应用程序调用 recv 读取。如果:

  • 接收方的应用程序读取速度太慢,导致缓冲区中堆积了发送方连续发来的多组数据;
  • 当应用程序终于调用 recv 时,会一次性把缓冲区中堆积的所有数据读走,从而造成粘包。

三、哪些场景下会遇到粘包?

不是所有 TCP 通信都会粘包,以下 2 类场景最容易出现:

场景类型典型例子粘包原因
发送方连续发小数据聊天软件连续发 2 条短消息(各 10 字节)发送方 Nagle 算法合并小数据包
接收方读取不及时接收方在处理前 1 组数据时,发送方又发了 3 组接收方缓冲区堆积多组数据

反之,如果发送方每次发大数据包(比如 10KB),或接收方即时读取(发 1 组读 1 组),粘包概率会极低。

四、解决粘包的 3 种核心方案

解决粘包的本质是:给 TCP 的字节流 “划分边界”,让接收方知道 “哪部分是一组数据”。核心方案有 3 种,实际开发中最常用前 2 种:

1. 方案 1:固定数据包长度(最简单)
  • 约定:发送方每次发送的数据长度固定(比如每次都发 100 字节);
  • 接收方:每次 recv 固定长度(100 字节),即使数据不足 100 字节,也按 100 字节读取(不足部分可补 0);
  • 优点:实现简单,无需额外处理;
  • 缺点:灵活性差 —— 如果数据实际长度远小于固定长度(比如只发 10 字节却要占 100 字节),会浪费带宽。

示例:发送方每次 send 前,把数据补到 100 字节(比如“hello”补 95 个 0);接收方每次 recv (100),再去掉补的 0,得到原始数据。

2. 方案 2:添加 “消息头”(最常用)
  • 约定:每个数据包分为 “消息头” 和 “消息体” 两部分;
  • 消息头:固定长度(比如 4 字节),用来存储 “消息体的实际长度”;
  • 接收方步骤:
    1. 先 recv 固定长度的消息头(4 字节),解析出消息体的长度(比如解析出是 20 字节);
    2. 再 recv 对应长度的消息体(20 字节),即可准确拿到一组完整数据;
  • 优点:灵活,不浪费带宽,适合任何长度的数据;
  • 缺点:需要自定义数据包格式,实现稍复杂。

示例:要发“helloworld”(10 字节),先构造消息头000A(4 字节,16 进制表示 10),再拼接消息体“helloworld”,最终发送“000Ahelloworld”;接收方先读 4 字节头,知道消息体是 10 字节,再读 10 字节即可。

3. 方案 3:使用 “分隔符”(适合文本数据)
  • 约定:在每组数据的末尾添加一个特殊的 “分隔符”(比如\r\n|,但要确保分隔符不会出现在数据本身中);
  • 接收方:持续读取字节流,直到遇到约定的分隔符,就认为从上次分隔符到当前分隔符之间的内容是一组完整数据;
  • 优点:实现简单,适合文本传输(比如 HTTP 协议的\r\n\r\n就是分隔符);
  • 缺点:如果数据本身包含分隔符(比如传输的文本里有\r\n),会导致误判,需要额外处理(比如转义)。

示例:发送方每次发数据后加\r\n,比如发“hello\r\n”“world\r\n”;接收方读数据时,遇到\r\n就分割,分别得到“hello”“world”

总结

粘包不是 TCP 的 “bug”,而是它 “面向字节流”+“缓冲区优化” 的必然结果。实际开发中,“固定头 + 消息体” 是兼容性最好、最常用的解决方案,几乎能应对所有场景。

TCP⼩结

为什么TCP这么复杂?因为要保证可靠性,同时⼜尽可能的提⾼性能.

可靠性:

  • 校验和
  •  序列号(按序到达)
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制

提⾼性能:

  • 滑动窗⼝
  • 快速重传
  • 延迟应答
  • 捎带应答

其他:

定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)

TCP/UDP对⽐

 我们说了TCP是可靠连接,那么是不是TCP⼀定就优于UDP呢?TCP和UDP之间的优点和缺点,不能简单, 绝对的进⾏⽐较

  • TCP⽤于可靠传输的情况,应⽤于⽂件传输,重要状态更新等场景;
  • UDP⽤于对⾼速传输和实时性要求较⾼的通信领域,例如,早期的QQ,视频传输等.

另外UDP可以⽤于⼴播; 归根结底,TCP和UDP都是程序员的⼯具,什么时机⽤,具体怎么⽤,还是要根据具体的需求场景去判定.

⽤UDP实现可靠传输(经典⾯试题)

核心设计目标

让 UDP 具备 TCP 的核心可靠特性:

  1. 数据不丢失(丢失重传)
  2. 数据有序到达(序号与重排)
  3. 流量控制(避免接收方缓冲区溢出)
  4. 拥塞控制(避免网络拥塞)

关键实现机制

1. 序号与确认机制(ACK)
  • 序号(Sequence Number):为每个 UDP 数据包分配唯一序号(按发送顺序递增),确保接收方识别数据顺序和丢失情况。

  • 确认(ACK):接收方收到数据后,向发送方返回 ACK 包,告知已成功接收的最大序号(或具体序号)。

  • 超时重传:发送方若在超时时间内未收到 ACK,重传对应数据包。

    :发送方发送包 1、2、3,若接收方只收到 1 和 3,会返回 ACK=1(表示已收到 1,等待 2),发送方超时后重传 2。

2. 滑动窗口机制
  • 类似 TCP 的滑动窗口,限制未确认数据包的最大数量,实现流量控制:
    • 发送窗口:发送方可连续发送的未确认数据包上限(由接收方的接收窗口大小决定)。
    • 接收窗口:接收方缓冲区的剩余容量,通过 ACK 告知发送方,避免发送方发送过快导致接收方溢出。
3. 超时重传与重传策略
  • 超时时间(RTO):动态计算(类似 TCP 的 RTT 估算),根据网络延迟调整,避免过早或过晚重传。
  • 快速重传:当接收方收到乱序包(如预期包 2,却收到 3),连续发送 3 次对包 2 的 ACK,触发发送方立即重传(无需等待超时)。
4. 拥塞控制
  • 模拟 TCP 的拥塞控制算法,避免大量数据包导致网络拥塞:
    • 慢启动:初始阶段,发送窗口按指数增长。
    • 拥塞避免:窗口达到阈值后,按线性增长。
    • 拥塞发生:超时或收到 3 次重复 ACK 时,减小窗口(如重置阈值为当前窗口一半,窗口设为 1)。
5. 数据校验与去重
  • 校验和:在 UDP 数据报中添加校验和,接收方验证数据完整性,丢弃损坏的包。
  • 去重:接收方通过序号记录已接收的包,若收到重复序号(如重传包),直接丢弃并返回对应 ACK。

实现流程概要

  1. 连接建立:类似 TCP 的三次握手(可选),交换初始序号、窗口大小等参数,确认双方通信准备就绪。
  2. 数据发送
    • 发送方按序号封装数据,在窗口范围内连续发送。
    • 维护未确认包列表,启动超时计时器。
  3. 数据接收
    • 接收方按序号缓存数据,若有序则提交给应用层,否则暂存乱序包。
    • 向发送方返回 ACK(包含已接收最大有序序号和接收窗口大小)。
  4. 重传处理
    • 发送方收到重复 ACK 或超时,重传对应数据包。
  5. 拥塞控制:根据网络状态动态调整发送窗口。
  6. 连接关闭:类似 TCP 的四次挥手(可选),确保双方数据传输完毕。

优缺点

  • 优点:相比 TCP 更灵活,可根据场景定制(如简化拥塞控制、调整超时时间),适合低延迟场景(如游戏、实时音视频)。
  • 缺点:实现复杂(需手动处理所有可靠机制),通用性不如 TCP,且可能因设计不当导致性能问题(如重传策略不合理)。

经典应用

  • 实时游戏:自定义可靠 UDP 协议,在保证关键数据(如玩家操作)可靠的同时,允许非关键数据(如场景细节)丢失,降低延迟。
  • QUIC 协议:基于 UDP 实现,集成了可靠传输、TLS 加密等特性,性能优于 TCP。

通过上述机制,UDP 可在应用层模拟 TCP 的可靠性,同时保留 UDP 低延迟的优势。

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

相关文章:

  • 理财网站开发最近中国新闻
  • 详解网络安全免杀对抗:攻防的猫鼠游戏
  • 【开题答辩全过程】以 高考志愿分析系统为例,包含答辩的问题和答案
  • ESP-IDF基础入门(2)
  • 中国建设官方网站首页网上商城推广方案
  • 网站建设必须安装程序天眼查公司信息查询
  • 织梦网站首页是哪个文件网站手机访问跳转代码
  • 博弈dp|凸包|math分类
  • 网站浏览器兼容性问题wordpress手机端网站
  • 中国建设银行预约网站xampp做网站
  • VS2019+CUDA 编译通过但有错误提示
  • 有哪些做问卷调查挣钱的网站单页 网站模板
  • 承德网站制作数据库营销案例
  • 32位汇编:实验9分支程序结构使用
  • Kanass实践指南(3) - 开发团队如何通过kanass有效管控开发任务
  • 基于双向时序卷积网络与双向门控循环单元(BiTCN-BiGRU)混合模型的时间序列预测(Matlab源码)
  • 电子商务网站建设 精品课wordpress主题缓存
  • 站建设 app开发网站网站建设中怎么添加源码
  • qq推广网站建立企业网站 优帮云
  • 【AHE数据集】历史/未来全球网格热排放数据 PF-AHF
  • phy降速自愈到100M重试流程分析
  • Spring+LangChain4j工程搭建
  • Raft协议
  • 快速建设网站视频教程网站内容质量
  • 建设银行网站登不上牛商网做网站的思路
  • C语言程序代码(四)
  • 仿射变换的图像配准技术
  • wordpress看文网站芜湖营销网站建设
  • 泰州企业建站程序做购物网站的图标从哪里来
  • 1. Linux C++ muduo 库学习——库的编译安装