其他分享
首页 > 其他分享> > 【计算机网络自顶向下方法】手把手带你设计一个可靠且高效的数据传输协议

【计算机网络自顶向下方法】手把手带你设计一个可靠且高效的数据传输协议

作者:互联网

文章目录

写在前面:

  1. 本文主要参考《计算机网络自顶向下方法》,主要为个人学习整理的记录,如果能够帮到同样正在学习计算机网络的你,那也再好不过了,一起进步!
  2. 读完本文,你将了解为实现可靠的数据传输都将面临哪些现实挑战,而校验和、序列号、ACK 应答、计时器、管道化传输、滑动窗口、选择重传这些机制又都是如何使得数据传输变得可靠和高效的!

1. 引言

稍微了解计算机网络的朋友都会知道的一点是,我们一般说 TCP/IP 协议栈中传输层的协议 TCP 是可靠的,而同样是传输层的协议,UDP 协议则是不可靠的。实际上,不仅对于传输层的部分协议,针对网络协议分层模型中的数据链路层和应用层,实际应用中也都对其中一些协议存在数据传输的可靠性要求。

由于数据传输可靠性的意义不言而喻,因此本文旨在讨论一般情况下,针对无论处于哪一层的协议,保证其实现可靠的数据传输需要哪些机制,以及这些机制是如何保证数据传输的可靠性,以此来加深读者对于 TCP 等协议是如何实现可靠数据传输这一点的理解。

为便于读者理解,下图首先给出接下来讨论所使用的框架,如图 a 所示,可靠的数据传输信道为上层的实体提供了服务的抽象,在可靠的信道内,传输的数据不会发生丢失或误码,而且所有数据在接收端被接收到的顺序和其在发送端被发送出去的顺序一致。实际上,这也是当互联网应用调用 TCP 协议发送数据时,后者向前者提供的服务模型。

上述提到的服务抽象实际上就是由可靠的数据传输协议负责实现的。事实上,要实现一个可靠的数据传输协议却并不简单,因为一个可靠的数据传输协议,其底层所依赖的传输链路可能并非是可靠的。例如:传输层协议 TCP 是一个可靠的数据传输协议,但其所赖以实现的网络层协议 IP 却并非是可靠的。

由于对于可靠的数据传输协议底层所依赖的传输链路来说,导致其不可靠本质的原因有很多;因此,接下来,本文将通过不断考虑更加复杂的情况,来尝试设计一个可靠的数据传输协议。例如:如果底层的链路或信道可能引起误码或丢包1,那么应该使用什么样的机制来解决。

在这里插入图片描述

上面的图 b 展示了后续即将设计的可靠数据传输所涉及的接口:

2. 设计一个可靠的数据传输协议

下面,本文将人为通过对底层信道或链路进行施加约束的方式,来一步步介绍怎么样在不断变得负责的情况下实现一个可靠的数据传输协议。

2.1 rdt1.0 假定底层信道也可靠

首先是最简单的情形,即假定底层信道也是可靠的。这是我们称该协议为 rdt1.0 ,下图给出了此时发送端和接收端的有限状态机,其中图 a 表示发送端的操作而图 b 表示接收端的操作。

在解释图中收发双方的状态机之前,有必要对图中的符号和文字含义解释如下:

下面是对于下图中 rdt1.0 收发双方状态机的详细解释:

在这里插入图片描述

在 rdt1.0 中,由于其构建在本身就是可靠的信道或链路之上,因此接收方不需要向发送方提供是否接受成功的应答。

2.2 rdt2.0 假定底层信道有误码

实际上,在实际中上述假设是不可能成立的,即底层的信道总是有可能出现误码6的,因此在 rdt2.0 中来讨论如何解决这个问题,同时这里依旧假设数据包在收发双方被发送或接受的顺序是一致的

在尝试解决上述问题之前,我们来看一个现实中的例子。假设你正在和别人打电话,而且这时候你需要将对面说的一大段内容都记录下来。在正常情况下,当你每记录一句话之后你都会和对方说“好的”,但如果你没有听清楚对方说的某一句话,此时你会跟对方说“请再重复一下”。

上面这个实际的例子其实就通过使用“肯定应答”和“否定应答”的方式来实现准确的信息交互。实际上,在计算机网络中也是同样的道理,且基于这种方式实现的可靠数据传输协议称为自动重复请求协议(ARQ: Automatic Repeat reQuest)。

由上述讨论可知,要应对可靠传输协议底层信道可能产生误码的情况,ARQ 类协议至少应该具体的机制如下:

下图是 rdt2.0 协议即采用了上述机制实现的可靠数据传输协议的收发双方有限状态机:

在这里插入图片描述

如上图 b 所示,接收端仅有一个状态:

上述 rdt2.0 看起来好像是一个可靠的数据传输协议,但实际上其有一个致命的缺陷,即 rdt2.0 没有考虑到 ACK 或 NAK 数据包也可能发生误码的情况。

为了解决这个问题,你的第一反应可能是给 ACK 或 NAK 数据包也加上校验和字段,然而更关键的问题不止于此,而是该协议如何能在发送方收到发生误码的 ACK 或 NAK 之后还能正确判断接收方是否准确的收到了刚刚的数据包,因此更关键的问题是发送方如何直接从产生误码的 ACK 或 NAK 得到期望的信息

下面考虑当 ACK 或 NAK 确实发生误码时,可能采取的 3 3 3 中处理方式:

2.2.1 rdt2.1

针对 ACK 和 NAK 可能发现误码这个问题,包括 TCP 在内的数据传输协议都采用了一种简单的方式来解决这个问题,即为发送方的数据包中新增一个字段,该字段填写的是发送方为数据包编号之后的数字即序列号,然后接收方只需要检查该序列号就能确定收到的数据包究竟是否为重传报文。

对于上述 rdt2.07 而言,使用 1 1 1 个比特位的序列号就能满足要求,因为这就已经能让接收方知道发送方究竟是在进行报文重传还是发送新的数据包,具体地:

需要说明的是,由于截至目前我们依然假设收发双方的信道不会产生丢包,因此 ACK 和 NAK 报文本身并不需要指定是针对哪个收到的数据包的应答反馈,即发送方天然地就知道其收到的 ACK 或 NAK (无所谓是否产生误码8)是针对其最近发出去的数据包的反馈。

上面增加了序列号的数据传输协议(以下称为 rdt2.1),相较于 rdt2.0 ,其收发双方的有限状态机都有更多的状态。原因在于,协议状态需要反映发送方当前发送的或者接收方期望接收的报文究竟序列号为 0 0 0 还是 1 1 1 。

下图分别是 rdt2.1 中发送方和接收方的有限状态机:

在这里插入图片描述

2.2.2 rdt2.2

上述 rdt2.1 使用 NAK 来表明其收到来自发送方的数据包有误码产生,实际上,也可以不用 NAK 而是仅使用 ACK 就实现同样的效果,即当接收方收到来自发送方的数据包有误码产生时,接收方向发送方应答针对上一个正确接收的数据包的 ACK ,如果提前约定好,那么此时发送方收到针对同一个数据包的两个 ACK 时,就知道接收方并未能正确接收紧随其后的数据包。

上面给出的不使用 NAK 而仅使用 ACK 作为接收方对所有收到报文(不管产生误码与否)进行应答的协议以下称为 rdt2.2 。rdt2.1 和 rdt2.2 之间的区别主要就在于,接收方在发送 ACK 应答时需要带上对应报文的序列号,而发送方在收到 ACK 时,也需要检查其中的序列号。

下图分别是 rdt2.2 中发送方和接收方的有限状态机:

在这里插入图片描述

2.3 rdt3.0 假定底层信道有误码和丢包

现在假定收发双方间的信道除了可能引起误码外,还可能产生丢包,实际上丢包在今天的计算机网络(包括因特网)中还是很常见的现象。信道丢包的可能又引出了对设计可靠数据传输协议的两大挑战:

实际上,rdt2.2 协议中已有的报文校验和、序列号、ACK 应答包和报文重传等机制已经可以解决上述第二个挑战了,而解决第一个挑战则需要新的协议机制。

这里,新的协议机制将会把负责探测丢包和丢包处理的任务都放到发送方来解决。

假设发送方发送了数据包之后,要么数据包本身,要么接收方针对该数据包的 ACK 应答包丢失了,此时都将导致发送方不可能收到接收方的应答。如果愿意等待足够长的时间,那么发送方是可以判断出数据包是否丢失的,但是问题是究竟多长时间算足够长?

首先,接收方的等待时间至少要大于收发双方间报文往返的时间(可能还要包括在二者中间的路由器中缓存的时间)加上接收方处理报文的时间等。实际上,在多数计算机网络中,这一所谓足够长的时间是很难精确计算的。

其次,为实现高效地数据传输,理想的情况下,丢包的问题应该尽快得到解决,否则将导致数据传输的效率急剧下降。

在实际中,发送方通过审慎地选择一个折中的时间长度,使得过了这么长时间后就判定数据包大概率已经丢失了,此时发送方将进行报文重传。

需要注意的是,如果一个数据包在传输过程中经历了特别长的时延,即使最终该数据包本身以及接收方的 ACK 应答都没有丢失,发送方也将进行报文重传,虽然这将会为收发双方的信道中引入重复的数据包,但幸运的是 rdt2.2 中的报文序列号机制已经可以处理这一情形了。

实际上,从发送者的角度可以说是“遇事不决,报文重传”了。当发送方并不知道究竟是数据包本身丢失或延时过长了,还是 ACK 应答丢失了抑或是延时过长了。在所有这些情况下,发送方的做法都一样:报文重传。

下图是 rdt3.0 中发送方的有限状态机:

在这里插入图片描述

由上图可知,相较于 rdt2.2 ,为了应对丢包的情况,这里引入了计时器,具体地:

为了更好地理解引入计时器机制的 rdt3.0 是如何在收发双方信道可能引起误码和丢包情况下进行可靠的数据传输,下面给出几个示例,分别表示 rdt3.0 在无丢包和有丢包的情况下是如何工作的:

在这里插入图片描述

截至目前,rdt3.0 已经是一个较为成形的可靠的数据传输协议了。

3. 设计一个可靠且高效的数据传输协议

3.1 管道化传输

上述 rdt3.0 在功能上已经基本可以实现可靠的数据传输了,但是其传输效率却差强人意,其本质原因是 rdt3.0 是一个停止-等待型协议

为了更好的理解为什么停止-等待型协议的效率低下,可以用下面这样一个例子:假设有两台主机,分别位于黑河和腾冲(如下图所示),报文在二者之间的往返传播时延(记为RTT)大概为 30 30 30 毫秒,假设两台主机之间通过传输速率为 R R R 为 1  Gbps 1\text{ }\text{Gbps} 1 Gbps (即每秒传输 1 0 9 10^9 109 比特)的链路相连。

假设每个数据包大小 L L L 为 1000 1000 1000 字节(约 8000 8000 8000 个比特,包括报文头和数据),因此将每个数据包传入二者链路所需时间为:
d t r a n s = L R = 8000  bits/packet 1 0 9  bits/sec = 8  ms d_{trans}=\frac{L}{R}=\frac{8000\text{ }\text{bits/packet}}{10^9\text{ }\text{bits/sec}}=8\text{ }\text{ms} dtrans​=RL​=109 bits/sec8000 bits/packet​=8 ms

在这里插入图片描述

通过下图 a 我们可以进一步量化停止-等待型协议效率低下的程度。如果发送方在 t = 0 t=0 t=0 的时候发送数据包,那么由上述计算:

因此,在整整 30.008  ms 30.008\text{ }\text{ms} 30.008 ms 的时间里,发送方只有 0.008  ms 0.008\text{ }\text{ms} 0.008 ms 的时间在发送数据。具体,可以计算发送方对信道的利用率为:
U sender = L / R R T T + L / R = 0.008 30.008 = 0.00027 U_{\text{sender}}=\frac{L/R}{RTT+L/R}=\frac{0.008}{30.008}=0.00027 Usender​=RTT+L/RL/R​=30.0080.008​=0.00027

在这里插入图片描述

解决上述传输效率低下问题的方法其实也比较简单,如上图 b 所示,即实际可以一次性发送多个数据包然后等待接收方对这些数据包的 ACK 应答,而不是以串行的方式一次只发送一个数据包然后等到收到 ACK 应答后才发送下一个。这种解决方式就是所谓的管道化数据传输

然而,管道化传输给可靠的数据传输又带来了新的挑战:

3.2 滑动窗口协议

在滑动窗口协议(又被称为GBN协议,全称Go-Back-N)中,发送方可以一次性发送不超过某个最大值如 N N N 个数据包。下图是从发送者角度来看显示的滑动窗口协议中序列号的范围和工作原理:

在这里插入图片描述

其中:

由此,所有可能的报文序列号就被分成 4 4 4 个区间:

由上图可知,长度为 N N N 的区间是所有可用于已发送但未收到 ACK 应答报文的序列号,该区间可视为一个窗口,在协议进行数据包收发期间,相当于该窗口在序列号所有可能取值的区间进行滑动,因此该协议被称为滑动窗口协议,而 N N N 被称为窗口大小。

需要注意的是,在实际中,滑动窗口协议中的报文序列号并非是只增不减,由于序列号这一数据域在数据包的数据头中是固定长度的,假设该长度是 k k k 个比特,那么序列号取值区间就是 [ 0 , 2 k − 1 ] [0,2^k-1] [0,2k−1] 。有人可能会问那如何确保数据包的数量不超过序列号的最大可能取值?

实际上,并没有必要有这样的要求,因为序列号是可以重用的,具体实现方式是对于序列号的所有数学运算都使用按 2 k 2^k 2k 取模的方式进行,这就意味着序列号区间可以视为一个环形区间,即序列号 2 k − 1 2^k-1 2k−1 后跟着的序列号9将会是 0 0 0 。

下面是基于滑动窗口协议实现可靠数据传输所对应的发送方有限状态机:
在这里插入图片描述

滑动窗口协议中的发送方必须响应以下三种事件:

下面是基于滑动窗口协议实现可靠数据传输所对应的接收方有限状态机:

在这里插入图片描述

对于上述滑动窗口协议,有些人可能觉得接收方的处理逻辑过于简单粗暴而且有浪费资源之嫌,他们的理由是接收方在除了序列号为 n n n 的数据包被正确接收并且序列号为 n − 1 n-1 n−1 的数据包也已经被正确接收的所有其他情况下,都会直接将数据包丢弃。

实际上,接收方这么做是有其合理性的。如上所述11,接收方必须按照数据包序列号向上一层传递数据。假设序列号截至 n − 1 n-1 n−1 的数据包都已经被成功接收且传递至上一层,此时虽然接收方期望收到序列号为 n n n 的数据包,但是实际先到达的是序列号为 n + 1 n+1 n+1 的数据包。当然,接收方可以先将序列号为 n + 1 n+1 n+1 的数据包缓存下来,等到正确接收且向上一层传递了序列号为 n n n 数据包后,再对缓存的数据包进行处理。

然而,如果此时序列号为 n n n 的数据包发生丢包,那么根据滑动窗口协议的重传规则,序列号为 n n n 和 n + 1 n+1 n+1 的数据包都会被重传。因此,接收方可以直接丢弃先到达的序列号为 n + 1 n+1 n+1 的数据包。

上述机制的优点在于,接收方的压根不需要实现缓存数据包的机制,因为没有必要。因此,虽然发送方必须要维护其窗口的上下界以及其中 nextseqnum 的位置,但是接收方仅需要维护下一个期望收到数据包的序号 expectedseqnum

上述机制的缺点在于,如果直接丢弃序列号不是 expectedseqnum 的数据包,那么如果后续对该数据包的重传发生丢包或误码,此时又会进一步导致更多的报文重传发生。

为了更好地理解上述介绍的滑动窗口协议,下面通过一个示例来说明,示例中窗口的大小为 4 4 4 :

在这里插入图片描述

下面是对上图简单说明:

3.3 选择重传协议

对于上述滑动窗口协议,虽然其具有提高信道利用率的功能,但是当有一个数据包的传输有问题时,可能就需要重传很多个数据包。下面即将介绍的选择重传协议就是为了缓解这样的问题。

顾名思义,选择重传协议只会重传那些在传输中发生错误的数据包(如:丢包、误码、时延过长)。这种按需重传机制需要接收方针对每个成功接收的数据包发送 ACK 进行确认,而不是采用累积确认。

在选择重传协议中,同样使用宽度为 N N N 的窗口来限制信道中已发送但发送方未收到 ACK 确认的最大数据包个数;不同的是,相较于上述滑动协议,此时在窗口中将会有部分数据包已经收到了接收方的 ACK 应答。

下图展示了选择重传协议中数据包序列号的分布特点和相关含义:

在这里插入图片描述

在选择重传协议中,发送方会针对任何正确接收的数据包发送 ACK 应答,不管其序列号是否紧跟上一个正确接收数据包的序列号。如果接收方收到的数据包后,发现其序列号的确不是按序递增的,那么该数据包将会先被缓存下来,然后等到所有小于该序列号的数据包都正确接收之后,会一股脑将这批数据包按序向上一层传递。

下面针对选择重传协议的收发双方所应该响应的事件和应该执行的操作进行了总结:

为了使读者更好地理解以上论述,下面通过一个案例来展示选择重传协议是如何处理丢包场景的:

在这里插入图片描述

4. 总结

至此基本就是本文的全部内容,这篇文章通过循序渐进的方式,通过假定收发双方间的信道越来越复杂,逐步引入各种机制,最终基本实现了可靠且高效的数据传输协议,在此将本文提及的各种机制总结如下表:

机制作用
校验和用于探测数据包在传输过程中是否发生了误码。
计时器用于在数据包或应答 ACK 发生丢包或者数据包传输时延过长时进行报文重传。
序列号用于对收发双方之间的数据包进行编号。
ACK 应答用于接收方告知发送方某个或某几个数据包已经被正确接收。ACK 应答报文中通常包含其所对应的数据包序列号。
NAK 应答用于接收方告知发送方某个数据包未能被正确接收。NAK 应答报文中通常包含其所对应的数据包序列号。
窗口,管道化通过允许最大不超过窗口大小个数据包同时发送,可以提高数据传输效率,这样的机制称为管道化传输。

  1. 需要注意的是,本文将自始至终假设数据包在信道或链路中被传递的顺序和其在发送端被发送的顺序是一致的,即虽然可能发生丢包,但是可靠的数据传输协议底层所依赖的信道或链路是不会改变数据包传递的顺序的。 ↩︎

  2. 这里 rdt 全称为 reliable data transfer 即可靠数据传输,而 _send 表示被调用的是发送端的可靠数据传输服务。 ↩︎

  3. 为简单起见,本文仅讨论单向的数据传输,即数据从发送端到接收端,实际上发送端和接收端是对偶的概念。 ↩︎

  4. 由于图中收发双方都只有一个状态,因此这里状态转移前后都是同一个状态,后续当情况更复杂时将出现多个状态之间的相互转移。 ↩︎

  5. 这里数据包(packet)和数据(data)的概念可类比于带包装的快递和拆除包装的快递。 ↩︎

  6. 所谓误码这里指数据在信道或链路的传输过程中比特位由 0 0 0 跳变为 1 1 1 或者由 1 1 1 跳变为 0 0 0 。 ↩︎

  7. 需要注意的是,这里当发送方处于等待ACK 或者 NAK 应答的状态时,发送方是不能继续从上一层接受数据的,即 rdt_send(data) 事件无法被触发,只有当发送方收到了 ACK 应答其才能转移到等待上层应用发起调用的状态。也就是说,发送方在确认接收方已经成功接收了最新的一个数据包之前是不会发送新的数据的,因此 rdt2.0 也被称为停止等待型协议(stop-and-wait protocol)。 ↩︎ ↩︎

  8. 实际上,这并不意味着 ACK 或 NAK 是否产生误码对发送方无影响,发送方还是需要知道收到应答报文内容究竟是 ACK 还是 NAK ,因为发送方接下来要据此做不同处理,即如果是 ACK ,即发送方将发送新的数据包,如果是 NAK ,发送方将进行报文重传。 ↩︎

  9. 值得一提的是,在 TCP 协议中,报文序列号并不是按照报文个数增加的,而是按照报文的字节数增加的。 ↩︎

  10. 在实际实现中,发送方一般会在这时候将数据进行缓存,或者采用一种同步机制(如:信号量、标志位)以实现只有当窗口未满时才可以调用 rdt_send() 接口。 ↩︎

  11. 值得注意的是,由于接收方收到数据包之后是挨个将其中的数据剥离出来后传递至上一层,这就意味着如果序列号为 k k k 的数据包被正确接收且其中数据被传递至上一层,那么所有序列号小于 k k k 的数据包也都已经得到了正确地处理。 ↩︎ ↩︎

  12. 在选择重传协议中数据包序列号的分布特点和相关含义那张图中,如果接收方收到了序列号为 send_base 的数据包而且也给发送方发送了 ACK ,但是该 ACK 发生了丢包,那么在计时器超时后发送方会重传该数据包,此时接收方可能再次收到该数据包,虽然之前接收方已经回复了 ACK ,但是整个过程中发送方是无从得知的,即如果接收方不再次针对这个数据包回复 ACK 的话,发送方就会反复重传该数据包,更重要的是发送方的窗口永远都不会向右滑动。 ↩︎

标签:ACK,手把手,报文,发送,自顶向下,数据传输,序列号,接收,数据包
来源: https://blog.csdn.net/weixin_37780776/article/details/122528005