前文:

HarmonyOS-ArkUI Web控件基础铺垫3--TCP协议- 从规则本质到三次握手-CSDN博客

HarmonyOS-ArkUI Web控件基础铺垫2-DNS解析-CSDN博客

HarmonyOS-ArkUI Web控件基础铺垫4--TCP协议- 断联-四次挥手解析-CSDN博客

TCP报文段大小

前文我们已经讲完了TCP的建立连接和断开连接的流程。接下来我们关注在传输数据这里。

当TCP经过三次握手建立连接后,就可以发送数据了。

但是网络发送数据,每次发送的不能过多,因为有限制,我们回顾一下数据链路层和网络层的协议。

图上红圈圈的位置,可以看出:

  • 数据链路层的帧数据,最大载荷为1500字节,称为MTU。此值为网卡和路由设备定义,通常是1500字节。
  • 而IP数据层的数据包数据,最大载荷为 65535 字节

如果IP数据层的数据超过了帧数据的容量,会引发IP分片的操作,把IP数据包切分成若干帧数据,这样传到对方。但是分片操作本身耗时。

TCP为了避免分片,其容量大小的参考值,便是MTU(1500字节)。

帧数据最大承载量 - IP数据包固定部分(20字节) - TCP数据包头固定首部长度(20字节)
= 1500 - 20 - 20
= 1460

对于一条连接中的每一个TCP包数据量的大小上限,在TCP协议里是有相关规定的。即最大报文段长度-MSS。 MSS的长度,通常情况下就是我们计算出来的1460。

这个信息会在三次握手的时候,传给彼此。通过选项字段来表示:

具体通知流程

  • 第一个握手,客户端选项中带MSS,告知服务端,自己的最大报文长度,假设1460.
  • 第二个握手包,服务端的数据包选项中也带MSS, 用来告知客户端,自己的最大报文长度, 假设1380

MSS协商其实就是双方告知自身接收能力。如客户端的1460 和服务端的1380。双方知道大小,那么发送的数据,最多也不对超过彼此的上限。

TCP报文数据校验和确保数据正确

当数据从发送方传到接收方的时候,传输过程中有可能因为以下原因,数据遭受损毁

  • 物理干扰: 如电缆噪声,电梯干扰,导致比特错误。
  • 设备故障: 路由器或者交换机硬件错误
  • 协议冲突: 分片重组错误或者中间设备修改数据。

校验和涉及的机制:

校验和,是发送方对即将发送的数据,按照一定的算法,对其内容进行计算,得出来的一串数据。得出来后放置在如图位置:

当发送方计算好校验和,接收方收到数据之后,对其数据内容进行相同的算法计算,如果算出来和这个校验和是一致的,则认为数据是可用的。 否则丢弃

校验和算法

简单的提一下校验和算法,具体思路是将数据按照规定组装,之后按照16位进行分组累加, 进位回卷至低位,最后取反。


def checksum(data):
total = 0
    for word in data:  # 每16位为1组
    total += word
if total > 0xFFFF:  # 处理进位
    total = (total & 0xFFFF) + (total >> 16)
    return ~total & 0xFFFF  # 取反

TCP包的顺序

TCP的包经过发送方发送之后,每个包的路由方式不一定一样,并且到达接收方的顺序也不一定一样。我们拿出之前文章中的图一看可知

我们要确保来接收的数据包能按照正确的方式组装,则,

  • 发送方发送数据的时候,对传输的包顺序进行编号
  • 接收方接收数据按照编号组装。

编号, 即我们之前学习的 seq字段!用这个来保障的。

Seq字段再理解

seq字段,是一个TCP连接全程都要用的字段,但是TCP的阶段不同,其代表含义不同。也就是这个空位置被多次使用,不同的情况不同的含义。但终归是用来确定次序的。其值的设计用法为:

阶段

seq值来源

相关FLAG

意义

握手阶段

随机生成

SYN=1

同步初始需要,建立可靠链接

数据传输阶段

前一段数据的seq + 偏移

SYN=0, FIN = 0

保障数据的有序性和完整性

挥手阶段

当前连接的最后数据序号

FIN = 1 SYN = 0(发送端)

安全关闭连接,确认所有数据

现在讲的是数据传输阶段,所以它的规则便是 前一段数据的seq + 数据偏移量。 那么第一个数据包传送的seq值则为: 三次握手最后一次发送方发出去的数据包的seq + 偏移量0 = 旧seq值。 设此值为n.

为什么传输数据的时候seq是这么个设计方式呢? 原因是便于包到达目的地之后,更快地映射到缓存的位置,我们根据偏移量就可以快速定位。

下图为单方向传递, 与接收方的数据拼接方式:

TCP丢包保障

数据包经由发送方发到接收方的途中,有数据包丢失的概率,注意,这不是数据因为某些干扰而损坏了,而是包不见了!这跟校验和解决的问题性质不同。比如断网了,包无法到达目的地。

超时重传

解决这种问题简单, 就是发送方,在发送数据的时候,起一个定时器,接收端在收到这个数据包的时候,返回一个对应的确认包,以通知发送方数据被签收了。如果在这个定时器范围内,得知数据被签收了,发送方正常执行,如果,定时器时间到了仍然没有收到确认包,则重新传一份数据包这就是超时重传

此处的确认包,我们称之为ACK(Acknowledgment)。

ACK再理解

ACK和ack字段,和seq字段,均具备不同阶段不同使命的特点。在TCP握手的时候,我们会用ack字段来确认包是第一次握手的还是第二三次握手的。

但是在数据传输的阶段,其存在的意义便转向了保障数据的有序性。如下表所示:

阶段

ACK标志位

ack确认号字段

核心作用

握手

声明确认功能生效(如SYN+ACK)

同步初始序列号(ack=x+1/y+1)

建立可靠传输起点

传输

所有报文强制置1

动态计算期望字节(ack=seq+len)

保障数据有序性与完整性

挥手

独立确认FIN(与FIN组合或分离)

精确响应FIN占位(ack=FIN_seq+1)

安全释放连接资源

目前是传输阶段,所以我们的ACK包里中 ack的意义,便变成了:期望哪段数据来!

重传机制中对ACK的使用

ACK包中ack的数据代表期望哪段数据来,同时也代表上一段数据肯定到了,要不然期望的就是上一段数据。也能侧面表达出发送传出的哪些包已经到了。

目前我们已经学到了定时器,和ACK, 下图为最简单的执行机制,这是最为简单的超时重传!

图中已经表明了,B,返回的ack的数值实际上就是期望的下一段数据的数值。一旦发回, 就代表上一段数据已经就位了。

同时图中也提示了超时重传这个细节,当A第一次发出2号包的时候,2号包在路上丢了,没有到达B, 那么B自然也不会给出ACK,A这边计时器便会触发,从而A会重新传一份2号包。

但是上图中,我们可以看出来,这个ack包是在收到A端的数据之后, B端才会发送,就这么一来一往的,这种一方收到数据后再发送回传的形式,叫做“停等协议”。

流水线协议

停等协议的效率可不高啊!因为A-B这段时间里, A做不了别的事情,并且A-B的链路明明可以一直发送数据,可现在的情况是必须等到B回传ACK包之后A才能发下一个。

在TCP中,肯定不会用这种模式的,TCP用的是"流水线协议", 流水线协议指的是,A方可以不等到B方来确认包,就把之后的包也传送出去。 不必非要等到确认包来才发下一个数据包。

流水线协议工作形式如下图所示:

从窗口到滑动窗口(演进)

窗口,是TCP协议中用来控制流水线协议的一个优化机制。窗口是TCP中的一个变量,其含义是:无需等待确认的,可以发送的数据包数量。单位为字节

为什么需要窗口?

对于流水线协议而言,如果不做任何处理,A不管不顾给B发送大量数据,非常容易出现问题:

  • A数据发送的太多,超过了B本身的处理能力,则B容易出现缓冲池溢出等问题。
  • A传送途中网络状况不好,则A方计时器会接二连三到点,会不断地重新发送。给本不给力的网络加重负担。最好的情况是,网络好,并且B承受得住,那么就一次性多发点。网络不好,或者B承受不住,则一次性少发点。对于B而言也是如此。

所以双方在进行传输的时候,是必然要知道彼此处理能力的。窗口就是用来同步这种处理能力的。

窗口的大小被记录在数据包里, Window Size字段所属区域中,单位为字节,用以告知对方自己的处理能力(自身处理能力 + 网络状况综合计算得出)。当双方都知道彼此的处理能力后,他们可以一次性发送指定大小的数据。

对于客户端和服务端双方,是在第二次握手的时候首次知道彼此的窗口大小的。当数据进行传输的时候,窗口的大小会根据网络状况和本地处理状况综合调整,并在发送包和响应包中记录,使双方动态调整。

窗口出现后,接收端有必要对发送端的数据 1v1发送ACK包么?

窗口的出现,代表发送方会一次性发送窗口指定大小的数据,等到对方窗口数据全部收到,再返回一个ACK包。ACK包的ack值,为期望的下一个数据的编号。这样就可以大大减少B发送给A的确认包数量。如下图所示,理想状况下,数据不丢失,则会出现这样的现象:

滑动窗口

上图中,两个设备下方的窗口,分别有一个蓝色框。 当A的蓝色框数据传输完并确认已经传输到位的时候,我们看见这个蓝色框便向下移动了四个数据包的空间,看起来像是在滑动。同时接收方也是这个样子。 我们把这种机制叫滑动窗口

滑动窗口内数据传输丢了如何处理

首先发送方本就具备超时重传的能力,我们前文已经讲到了。发送方每发送出一个数据包就会起一个计时器,计时器如果到时见仍未收到接收方的ACK,则重传。发送方的超时重传,仅仅是一个兜底保障而已。如果仅仅依赖发送方的超时重传,则效率一定低下,分析如下:

窗口的机制后,情况变了,接收方正常情况下会在窗口数据全部收到后,发送一个ACK,这个ACK的序号则为对下一段数据的期望号。

那如果接收方的接收窗口迟迟不满,难道还不通知发送方了?非要指望触发发送方的超时重传机制,让发送方被动触发,重新发送,发送的还是整个窗口的数据?这种效率实在没法保障。

接收方对重传的支持-延迟ACK

接收方为此做了不少支持。方案为延迟ACK, 延迟ACK的解决思路如下:

接收方内部存在一个计时器,为计时器,默认200毫秒。假设有数据包 1 2 3 4被发送端发送,1 号包到达接收端,此时按照之前的规则,应是马上发送ACK=2的确认包。 但是实际上 2 号包很可能在路上马上就要到了。于是此时便会开启计时器,延迟发送ACK,如果这200毫秒内2号包来了,那么此时期待的包便变成了3号包,就马上刷新计时器,准备200毫秒后发送对3号包的确认包,ACK=3。以此类推。如图所示:

以上为正常情况的处理情况。

那么如果 1 2 3 4 包传输的途中 2 号包丢了呢?

则接收方接收到的包肯定是乱序包。 即 1 3 4 。 如果收到 1 包,流程正常执行,期望的包就是2号包。但是之后收到的包却是 3 和 4, TCP有个规则是,必须按照顺序确认数据包,但是目前来的 3 和 4 , 却没有2,显然是乱序的。则对3 4 包则会执行,只接受但不确认的操作。那么这样的话,窗口内期待的下个包依旧是2。等到计时器到了时间,仍会发送ACK=2.

但是仅仅这个机制还不够,因为接收端收到ACK=2的确认包时, 它就会从2开始重新发送,是从2开始,34包之后也会连带发过去。这样又会出现问题。

  • 会出现发送方多发送重复包的问题。理想情况下不要发这些。
  • 等接收方计时器到时间了再通知,有些滞后。最好是检测到对应的包没来,立即发送。

经查历史,TCP协议,在1981年,的确就是靠 发送端超时重传 + 延迟ACK来保障数据可靠性的。 也的确很长一段时间存在效率问题。

快速重传

快速重传是 1996 年, Van Jacobson提出的优化方式,作为拥塞控制算法的一种优化。 这个方案非常取巧!涉及的改动也很清晰。

它的规则是:

  • 当接收方接收的数据包,序号出现混乱的时候,则立即发送缺失的第一个包的ACK。
    • 如: 发送方传送 1 2 3 4 5 6, 接收方首先连续收到了 4 , 5, 3。那么显然此时的接收窗口是缺少 1 , 2 的。按照规则:那么接收方会连续发送三个 ack = 1的。注意是 = 1, 因为1,就是最早的缺失的包 。 意味着希望发送方从1开始传。
  • 当接收方收到3次及以上ACK序号一样的包,则立即开始传ACK上标注的包!不用管自身的超时计时器。

此规则巧妙之处分析

  • 此处巧妙之处在于: 如果频繁出现乱序,只会刺激最早的一个ACK包发出去,那么连续三个ACK包发出去是一件很简单的事。接收端很快接受到连续三个,则会触发第二个条件。发送方就会忽略计时器马上发送指定的包。
  • 我们拿刚才的例子,再来看。假设发送端发送了1, 接收端接到了,那么下一个ack则变为了新的最早的包, 就是2。 如果此时2 还没来, 则接收在接收到这个 1 的包的时候,便自动触发 ack=2 。如果2 恰好赶到了 2 的超时时间,还会触发ack=2, 这样就两次了!如果再接收到一个序号为6的包,那么便会触发第三次 ack=2。重传很容易被触发。
  • 假设, 没有6的到来,那么发送方此时只会触发两次ack=2. 但是介于发送方本来就有超时重传的兜底。此刻也会触发包2 的重传。

SACK-选择性确认

上方的分析如果仔细看仔细考虑我们仍会发现一个逻辑漏洞, 就是发送端 2 号包会超时,引发2号包重传。 但是 3 4 5 6 包呢?我们从头到尾都没有传什么ack=3 4 5 6这类的确认包。当接收方接收到2 号包的时候,即使知道 3 4 5 6 存在,接下来发送 ack = 7, 那也停止不住原先发送方已经定好的 3 4 5 6的计时器的。倘若ack=7的包回传的慢,在定时器出发之前没到达发送方。包存在发送方没办法用代码解决的问题。 这里面存在逻辑漏洞。 3 4 5 6 的计时器一定要及时停掉。停掉的条件只能是发送方尽早知道 3 4 5 6到达接收方了。 那么怎么知道有没有到达呢?

SACK(SelectiveAcknowledgment选择性确认),1996 年, Van Jacobson提出的解决方案。 从时间节点来看,是和快速重传一块提出来的。。所见略同啊。它的引入就是为了解决发送方不知道该重传那些数据的问题。使得接收方能够告知发送方已收到的非连续数据段。这样发送方只需要重传丢失的数据段而不是整个发送窗口。从而提升传输效率。

SACK字段只出现在接收方接收数据乱序,导致接收窗口中存在空洞时,此时发送确认包的时候便会带上SACK。

首先讲一下什么是空洞

空洞就是一个接收窗口接收数据的时候,出现乱序,导致数据不连续。中间暂时没有到的数据便会形成空洞。

SACK字段记录内容

SACK记录的格式为 【连续数据块的起始序列号-这个连续数据块的结束序列号 + 1】被记录在TCP数据包的选项字段中。对于选项字段的格式,我们在文章最开始的部分已经讲解。SACK对应的选项 kind=5

接收方检查出现空洞后,便会在ACK包中加上SACK, 来告知发送方,虽然我缺 ack = xx的包,但是,我最近的连续区域为 【a b】。 这样发送端拿到ACK包便知道接收端详细的”近况“。从而推算出应该重传的包。而不必发送那些 【a b】偏移区域对应的包了。

如下图所示:

DSACK - 重复选择性确认

DSACK是在SACK基础上扩展的一个选项类型。相当于SACK的增强版,因为它作为选项类型,其内容有一部分包含了SACK的功能,又有自己的功能。他主要是为了解决,发送方因网络延迟,不断发送重复包的问题。对于接收方收到重复包,能通知对方则应尽早通知对方。DSACK里面的内容,存放了重复接收的数据范围。同时也存放了老的SACK中指定的,已收到的连续数据范围。 全乎了。

DSACK 选项类型 kind值和SACK值采用的是一个, 为5. 但是数据格式可以在解析的时候区分究竟是SACK还是DSACK可以根据格式解析。如下:

ACK=2001, SACK=[DSACK:1000-2000] [SACK:3000-4000]
(期望)  (1000-2000重复接收)     (下个连续的数据为 3000-4000)

总结

我们稍微总结一下,TCP为了保证可靠快速传输,做的支持的机制:

  • 流水线协议 + 滑动窗口配合
  • 发送端的超时重传机制
  • 古早TCP版本的 接收方ACK延迟机制
  • 后期优化: 快速重传 + 选择性确认(SACK) + 重复选择性确认(DSACK)

下文链接

HarmonyOS-ArkUI Web控件基础铺垫6--TCP协议- 流量控制算法与拥塞控制算法-CSDN博客

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐