持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天
最近也是用了几天时间读了一下top-down中运输层的可靠数据传输原理,之后看了一下科大的网课。其中的内容也是相当多,一节课的内容足足学了几天。在写这篇文章前,关于计网我从来没写过以书中知识为内容的博客,因为在之前的第一章总述以及应用层的学习中,计网都给我一种知识点很零碎的感觉,第一章相当于是把整本书的内容做了一个缩略的介绍,之后是应用层,应用层有很多协议,每个协议都有自己的知识点,所以没办法一条线把它们串起来。
但学到运输层给我的感觉就不一样了,先学了运输层的一个职责(把网络层主机到主机间的通信转换为不同主机上进程与进程之间的通信),接着又学习了多路复用和分解的技术,这是实现运输层职责的关键。至此,我们可以用多路复用和解复用技术完成运输层的一个基本职责。但是我们知道,网络层的IP协议向上层交付的数据是尽力而为的交付,也就是不可靠的服务,那么在应用层能否把不可靠的服务转变为可靠的服务呢?在学习这一章之前,我们在早期的学习中了解到这是TCP协议可以实现的功能,在学习TCP如何实现这些功能前,我们先来看如何来传输可靠的数据,即——可靠数据传输原理。
问题描述
在正式开始学习前,我们要写了解这个问题。我们知道,计算机网络的每一层都是接受下层的服务然后实现功能为上层提供服务。我们假设提供可靠数据传输(rdt)的是运输层(实际上rdt的实现也会在链路层以及应用层),图示说明了我们学习可靠数据传输的框架:为上层提供服务的抽象是:数据可以通过一条可靠的信道传输,借助与可靠信道,数据就不会损坏或丢失,且按顺序交付。
但刚刚提到过,网络层IP协议提供的是不可靠的服务,所以在运输层中,我们需要实现一些协议,来把不可靠的数据传输变为可靠的数据传输,这个协议就是可靠数据传输协议(rdt) 。我们还是以应用层,运输层,网络层为例:
- 调用rdt_send() 函数,上层可以调用rdt发送方,把要传输的数据交付给它
- rdt的发送方和接收方都需调用udt_send() 发送分组给对方(udt为不可靠数据传输)
- 当分组通过信道到达接收方时,调用rdt_rcv()
- 通过deliver_data() ,rdt向上层交付数据
研究方法
我们知道,一个可靠的数据传输协议可以使数据不受损,不丢失,不乱序。现在我们将一步步的研究rdt协议,先从最简单的情况考虑起,一步步构造一个完美、可靠的数据传输协议。在正式开始之前,我们要先知道:底层的可靠性决定了rdt协议实体的复杂度。什么意思呢?还是拿IP协议举例,如果IP自己就可以实现可靠的数据传输,那我运输层只需要做一个多路复用就可以了,此时的rdt协议便非常简单。所以我们先假设IP协议是一个完美无缺的可靠性数据传输协议,再一层层放开这个假设,最终使IP协议变为一个不可靠的(即现在的IP协议的样子),在此过程中,我们的rdt协议也将一点点复杂起来。此外,我们使用有限状态机(FSM) 来描述发送方和接收方的状态。
rdt1.0(经完全可靠信道的可靠数据传输)
首先我们考虑最简单的情况,即底层信道是完全可靠的,没有比特出错,也没有分组丢失。那么此时发送方和接收方的FSM为:
- 发送方从上层拿到要方式的数据,将数据发送至下层信道
- 接收方从下层信道接收数据,将数据deliver到上层
rdt2.0(经有比特差错信道的可靠数据传输)
在rdt1.0中,我们假设底层信道是完全可靠的,现在我们放开一点这个条件,我们假设经底层信道传输的数据可能会出现比特差错,其余不可靠的情况不会出现。那么此时,rdt协议应如何设计呢?我们不妨来设想一个场景中的三种情况:
- 在课堂中,老师在课堂上提问你,这道题的答案是什么,在通常情况下,你回答了老师,老师会对你说“OK”,但如果你在回答的时候没有说清楚,老师可能会让你“重复一下你的回答”,人们是这样的,协议亦是如此。在接收方收到正确的分组时,它会告诉发送方一个肯定确认(ACK),相反就告诉发送方一个否定确认(NAK)。
- 现在再次回到我们的课堂场景,在老师向你提出“请重复一遍你的回答”后,你会再向老师说一遍这个答案。在协议中,如果接收方给发送方一个否定回答,那么发送方将会重传该分组,基于这样重传机制的可靠数据传输协议称为自动重传请求协议(ARQ) 。
- 最后再来思考一个问题,老师可以辨别你答案是否清楚明确是因为老师有自己的判断机制,当你没说清楚时老师可以直接听出来。那么放在协议中,接收方怎么知道发送方传的分组是错的呢?还记得在学习UDP的时候,UDP校验和提供了差错检测功能,在rdt协议中,我们同样可以使用校验和来进行差错检测。
至此,针对经有比特差错信道的可靠数据传输,rdt也通过了上述三种功能来解决它,即差错检验、接收方反馈以及重传。以下为rdt2.0中发送方与接收方的FSM,我们分别来盘一下:
-
对于发送方:接收上层的数据后,封装好并设置好校验和,发送给接收方后,自身变为等待ACK或NAK的状态,若收到接收方的NAK,则重发分组;若收到接收方的ACK,发送方转变为等待上层调用的状态。
- 需要注意的是,发送方在等待ACK或NAK状态时不能从上层获得更多的数据,即不会出现rdt_send()事件,发送方也不会发生新的数据,仅当接收到ACK离开此状态才会继续发送新的数据。这样的协议被称为停等协议。
-
对于接收方,接收分组后
- 如果corrupt(腐败),代表没有通过校验,即发生了比特差错,则发送NAK给发送方。
- 如果notcorrupt(未腐败),代表通过校验和,则发送ACK给发送方。
rdt2.1(解决rdt2.0的致命缺陷)
在刚刚的rdt2.0的假设中,我们只考虑到了分组在传输信道中受损的情况,如果接收方给ACK,发送方就传下一个分组,如果接收方给NAK,发送方就重传当前分组,但我们没有考虑到ACK或NAK分组受损的可能性。即如果接收方给发送方的ACK或NAK受损后变成了“乌拉乌拉”,那么2.0的发送方将直接懵比,所以为了解决这个问题,我们让发送方对其数据分组进行编号。而编号也并非需要很多序号,只需要0,1两个即可,分别表示前一个分组以及一个新分组两种状态。以下为rdt2.1中发送方和接收方的FSM,一眼看上去好像很复杂,我们一点点来看:
发送方
大概可以看出发送方的状态数变成了2倍,因为要区分0和1
-
上层通过rdt_send()调用后,打包分组,设置好校验和发送,这和rdt之前的版本无任何区别
-
发送分组0后,发送方转换为等待ACK或NAK0状态
- 若接收方给出ACK,则转换为等待上层的调用1状态
- 若接收方给出NAK0或接收方给出的确定受损,给出“乌拉乌拉”,则重发0号分组,直到收到ACK
-
发送1号分组以及等待ACK或NAK1两个状态和上面相同,不再说明。
接收方
通过大体的浏览这个图我们大概可以看出来接收方一个状态是有三个动作的,我们一个个来看,还是拿收到分组0来看,分组1原理相同
- 收到分组0,但corrupt(校验出错),发送NAK给发送方
- 收到分组0,校验没出错,发送ACK给发送方,自己转换到等1的状态
- 在等0的时候收到了1(左二行),则发送ACK给发送方,因为只有这样发送方才能发1
其中第三点需要注意一下,在等0的时候收到了1,就意味着接收方自己在上一次的分组确认受损了,导致发送方收到了乌拉呜啊,就重新把上一个分组发送了一遍,此时接收方要给出ACK确认该分组,使发送方发送下一个分组
为了方便理解,贴一张实际运行中可能出现的情景:
rdt2.2(无NAK)
rdt2.2在功能上和2.1无任何区别,只是把否定确认NAK变为对前一个分组的ACK,即如果收到1,校验失败,按照rdt2.1应发送NAK给发送方,在rdt2.2则直接发送ACK0即可,这样就可以实现一个无NAK的可靠数据传输协议。
这里还是带入一个生活中的例子,是中科大的郑老师在课堂上讲的,觉得很有意思,这里也分享出来。假设一个女生问你:我温柔吗?你觉得她很温柔,于是回答她:你很温柔。接着她问你:我漂亮吗?你觉得她在你心中不漂亮,于是你回答她:你很温柔。这就是rdt2.2,对本次分组的否定用对上一个分组的肯定来代替。如果按rdt2.1的逻辑来,你应该直接回她:你不漂亮。
所以rdt2.2发送方和接收方的FSM和2.1的区别就在于,等待ACK或NAK的状态变为等待ACK0(等0的情况)。
rdt3.0(经有比特差错的丢包信道的可靠数据传输)
在前面的讨论中我们主要针对比特差错进行了学习,但在数据传输时,除了数据可能会出差错外,丢包也是经常会出现的问题(丢包包括丢数据或丢确认信息),那么针对丢包,我们又该如何解决呢?解决步骤大概分为两步,第一步就是如何检验丢包,第二步就是检查出丢包后我们该做些什么。
我们令发送方负责检查和恢复丢包工作,采取的方法是让发送方选择一个时间值,如果在这个时间内没有收到确认信息,就判定发生了丢包。之后将重新传该分组。(一个分组如果经历了很长的时延,发送方可能会重新发送)
既然设置了一个时间值,我们就需要一个倒计数定时器来计时,如果timeout,则判定丢包,重发分组。所以发送方应做到:
- 每发送一个分组,启动一个倒计数 定时器
- 响应定时器中断
- 终止定时器
rdt3.0发送方的FSM: 在分析是还是拿发送0来举例
- 收到上层调用后,打包分组,设置好校验和,启动计时器,发送后进入等待ACK0状态
- 若接收方给出ACK1或乌拉乌拉,重发0,这是rdt2.2的内容
- 若接收方校验成功,给出ACK给发送方,则发送方停止这个分组的计时器
- 若在等待ACK0状态中一直没有收到确认信息,等待超时后,重传该分组。
以上就是发送方的动作,接收方的动作和rdt2.2相同,因为解决丢包这事全是发送方干的。为了便于理解,这里还是贴一张实时运行中可能出现的情况,因为无丢包出现的时候流程和rdt2.2相同,所以这里只画出 出现丢包操作时的流程