一、可靠传输数据原理
可靠数据传输的实现问题不仅在运输层出现,也会在链路层以及应用层出现。图3-8图示说明了我们学习可靠数据传输的框架。为上层实体提供的服务抽象是:数据可以通过一条可靠的信道进行传输。借助于可靠信道,传输数据比特就不会受到损坏(由0变为1,或者相反)或丢失,而且所有数据都是按照其发送顺序进行交付。这恰好就是 TCP向调用它的因特网应用所提供的服务模型。
实现这种服务抽象是可靠数据传输协议(reliable data transfer protocol)的责任。由于可靠数据传输协议的下层协议也许是不可靠的,因此这是一项艰巨的任务。
相关参数解释
- rdt_send()函数(其中rdt表示为可靠传输协议,_send表示rdt的发送端正在被调用)在应用层和运输层之间,可以理解为门岗,应用层和传输层交互数据需借助该函数。
- udt_send()函数(其中udt表示为不可靠传输协议,_send表示udt的发送端正在被调用)在网络层和下层(链路层)之间,也可以理解为门岗,网络层和链路层交互数据需要借助该函数。
- rdt_rcv()函数用于从底层信道(网络层)接受分组。
- deliver_data()函数用于交付给应用层。
二、构造可靠传输协议
有限状态机(Finite State Machine),是一种表示“有限个”状态以及在这些状态之间进行转换(Transition)和动作(Action)等行为的数学模型。它是算法思想的一种体现,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。如下图所示:有限状态机由边、节点构成。
- 节点:表示状态
- 边:表示状态到状态的迁移
- 图中分子上方的表示发生的事件,分母表示发生这个事件时采取的动作。
- 虚线:初始化状态
- 状态:表示系统在任何给定时间点的行为或状况。在FSM中,状态是有限的,并且每个状态都有一个唯一的标识符。
- 事件:导致系统从当前状态转换到另一个状态或执行某个动作的外部或内部刺激。
- 动作:系统在特定状态下对特定事件作出的响应。动作可以是执行某个操作、改变系统状态或产生一个新的输出。
- 转换:表示系统从一个状态到另一个状态的移动。转换通常是由某个事件触发的,并可能伴随着一个或多个动作的执行。
(一)经完全可靠信道的可靠数据传输:rdt1.0
- 适用于完全可靠的信道。
- 在这种情况下,发送方只需将数据发送出去,接收方就能准确无误地接收到,无需额外的差错检测和重传机制。
1.发送端:
- 等待来自上层的调用。发送端首先处于等待状态,等待来自上层(如应用程序)的数据传输请求。
- 发送端通过rdt_send(data)事件接受来自高层的数据,经过make_pkt(data)动作产生一个包含 该数据的分组。
- 该分组通过介于运输层和网络层的udt_send(packet)动作发送到信道。
- 发送端分组发完后重新进入“等待来自上层的调用”状态。
2.接收端:
- 等待来自上层的调用。接收端首先处于等待状态,等待来自下层(如网络层)的数据包到达。
- rdt_rcv(packet)事件:当接收到数据包时,接收端调用rdt_rcv函数来处理数据包。这个事件负责从数据包中提取数据,并准备将其交付给上层。
- 动作1:extract(packet, data):rdt_rcv函数内部会调用extract函数,从数据包中提取出数据部分。
- 动作2:deliver_data(data):提取出数据后,rdt_rcv函数会调用deliver_data函数,将数据交付给上层(如应用程序)。
- 接受端将数据传递给上层后重新进入“等待来自下层的调用”状态。
(二)经具有比特差错信道的可靠数据传输:rdt2.0
rdt2.0是一种可靠数据传输协议,它在rdt1.0的基础上增加了差错检测机制。rdt2.0假设底层信道可能会出现比特差错,但不会出现分组丢失。因此,它需要通过差错检测来确保数据的正确性,并在必要时请求重传。
rdt2.0中发送端有两种状态,接受端有一种状态:
- 发送端:状态1,等待来自上层的调用;状态2,等待ACK或NAK 。由于发送方需要等待接受端确认,因此rdt2.0协议被称为停等(stop-and-wait)协议。
- 接受端:等待来自下层的调用 。
1.发送端:
- 等待来自上层的调用:发送端处于等待状态,等待上层应用发送数据。
- rdt_send(data)事件:
- 当接收到上层数据时,调用此函数。
- 该函数负责将数据封装成数据包,并准备发送。
- rdt_send(data)事件对应动作:
- make_pkt(data, checksum):构造数据包的函数它接受数据和校验和作为输入,并返回封装好的数据包。
- 该分组通过介于运输层和网络层的udt_send(packet)动作发送到信道。
- 等待ACK/NAK:发送端在发送数据包后,会等待接收端发送的确认应答(ACK)或否定应答(NAK)。如果收到NAK,则表明数据包传输有误或丢失,需要重新发送;如果收到ACK,则表明数据包已成功接收。
- 收到NAK分组。
- 对应的事件:rdt_rcv(rcvpkt)&&isNAK(rcvpkt),表示接收分组有误。
- 对应的动作:udt_send(snpkt),一是对上个分组重新发送,二是等待接受方的确认。
- 收到ACK分组。
- 对应的事件:rdt_rcv(rcvpkt)&&isACK(rcvpkt),表示分组正确传输,发送端进入“等待来自上层的调用”。
- 对应的动作:
,表示不采取任何动作。
- 收到NAK分组。
2.接收端:
- 等待来自下层的调用:接收端首先处于等待状态,等待接收来自下层(如网络层)的数据包。
-
接收数据包并进行检查:当接收到数据包时,接收端会调用
rdt_rcv(rcvpkt)
函数来处理数据包。这里的rcvpkt
是接收到的数据包。-
接收到错误分组对应的事件和动作。
-
事件:rdt_rcv(rcvpkt)&&corrupt(rcvpkt),表示接收分组有误。(corrupt:腐败)
-
动作:产生make_pkt(NAK)函数用于生成包含NAK信息的数据包,通过udt_send(sndpkt)发送出去。
-
-
接收到正确分组对应的事件和动作。
-
事件:rdt_rcv(rcvpkt)&&nocorrupt(rcvpkt),表示接收数据分组无误。
-
动作:
-
extract(rcvpkt, data):这个函数用于从数据包中提取数据。
rcvpkt
是接收到的数据包,data
是提取出的数据部分。 -
deliver_data(data):将接收端成功解密(或验证无误)的数据
data
传递给上层应用程序。 -
snkpt=make_pkt(ACK):产生ACK分组。
-
udt_send(snpkt):将数据包发送出去。(snpkt表示待发送的数据包)
-
-
接收端重新进入“等待来自下层的调用”状态。
-
-
(三)经具有比特差错信道的可靠数据传输改进版本:rdt2.1
rdt2.0协议似乎可以运行了,但是存在致命的缺陷。尤其是没有考虑到ACK或NAK分组受损的情况。解决该问题的办法,rdt2.1协议让发送方对其数据分组编号,即将发送数据分组的序号。
rdt2.1中发送端有4种状态,接受端有2种状态。这是因为协议状态必须反映目前(由发送方)正发送的分组或(在接收方)希望接受的分组的序号是0还是1 。在停等协议(RDT2.1)中,序列号通常只有两个值(如0和1),用于交替发送和确认数据包。
- 发送端:
- 状态1,等待来自上层的调用0;
- 状态2,等待ACK或NAK 0 。
- 状态3:等待来自上层的调用0;
- 状态4:等待ACK或NAK 1。
- 接受端:
- 状态1:等待来自下层的 0
- 状态2:等待来自下层的 1
1.发送端:
- 等待来自上层的调用 0:发送端处于等待状态,等待上层应用发送数据。
- rdt_send(data)事件:
- 当接收到上层数据时,调用此函数。
- 该函数负责将数据封装成数据包,并准备发送。
- rdt_send(data)事件对应动作:
- make_pkt(0,data, checksum):构造数据包函数它接受序号0、数据、校验和作为输入,并返回封装好的数据包。
- 该分组通过介于运输层和网络层的udt_send(packet)动作发送到信道。
- 等待ACK/NAK 0:发送端在发送数据包后,会等待接收端发送的确认应答(ACK)或否定应答(NAK)。如果收到NAK,则表明数据包传输有误或丢失,需要重新发送;如果收到ACK,则表明数据包已成功接收。
- 收到NAK 0 分组。
- 对应的事件:rdt_rcv(rcvpkt)&&nocorrupt(rcvpkt)&&isACK(rcvpkt)),表示接收分组有误.
- 对应的动作:udt_send(snpkt),一是对上个分组重新发送,二是等待接受方的确认 0。
- 收到ACK分组 0。
- 对应的事件:rdt_rcv(rcvpkt)&&corrupt(rcvpkt)||isNAK(rcvpkt)),表示数据分组传输正确,发送端进入“等待来自上层的调用 1”。
- 对应的动作:
,表示不采取任何动作。
- 收到NAK 0 分组。
- 后续“等待来自上层的调用 1”的两个状态与“等待来自上层的调用 0”一致,应用层对数据分组的传递就是使用0和1 两个序号之间循环往复,不在进行赘述。
2.接收端:
- 等待来自下层的 0:接收端首先处于等待状态,等待接收来自下层(如网络层)的数据包。
-
接收数据包并进行检查:当接收到数据包时,接收端会调用
rdt_rcv(rcvpkt)
函数来处理数据包。这里的rcvpkt
是接收到的数据包。-
接收到错误分组对应的事件和动作。
-
事件:rdt_rcv(rcvpkt)&&corrupt(rcvpkt),表示接收分组有误。(corrupt:腐败)
-
动作:产生make_pkt(NAK,checksum)函数用于生成包含NAK信息的数据包,通过udt_send(sndpkt)发送出去。
-
-
接收到正确分组对应的事件和动作。
-
事件:rdt_rcv(rcvpkt)&&nocorrupt(rcvpkt)&&has_seq0(recvpt),表示接收数据分组无误。
-
动作:
-
extract(rcvpkt, data):这个函数用于从数据包中提取数据。
rcvpkt
是接收到的数据包,data
是提取出的数据部分。 -
deliver_data(data):将接收端成功解密(或验证无误)的数据
data
传递给上层应用程序。 -
snkpt=make_pkt(ACK,checksum):产生带有ACK、校验和的数据包分组。
-
udt_send(snpkt):将数据包发送出去。(snpkt表示待发送的数据包)
-
-
接收端重新进入“等待来自下层的1”状态。
-
-
- 后续“等待来自下层的 1”的两个状态与“等待来自下层的 0”一致,应用层对数据分组的传递就是使用0和1 两个序号之间循环往复,不在进行赘述。
(四)经具有比特差错信道的可靠数据传输改进版本:rdt2.2
rdt2.2是在有比特差错信道上实现的一个无NAK的可靠数据传输协议。rdt2.1和rdt2.2之间的细微变化在于,接收方此时必须包括由一个 ACK 报文所确认的分组序号(这可以通过在接收方FSM中,在make_pkt()中包括参数ACK0或ACK1来实现),发送方此时必须检查接收到的ACK报文中被确认的分组序号(这可通过在发送方FSM 中,在isACK()中包括参数0或1来实现)。如果期待收到1,那么收到接收方对0的确认,表示分组出错。发送方通过检验接收方发送来的ACK的序号来确定分组是否出错,是否需要重传。
- 以下是RDT2.0、RDT2.1和RDT2.2之间的区别的表格呈现:
RDT2.0 | RDT2.1 | RDT2.2 | |
---|---|---|---|
信道特性 | 具有比特差错的信道 | 同RDT2.0 | 同RDT2.0 |
差错检测 | 使用校验和检测比特差错 | 同RDT2.0 | 同RDT2.0 |
确认机制 | 使用ACK和NAK | 使用ACK和NAK,并引入序列号 | 仅使用ACK(带序列号) |
ACK/NAK作用 | ACK:分组接收正确;NAK:分组接收错误 | 同RDT2.0,但序列号帮助解决ACK/NAK可能发生的比特位翻转问题 | ACK:表示分组接收正确或请求重传(通过序列号) |
重传机制 | 接收方发送NAK,发送方重传出错分组 | 接收方发送NAK(含序列号),发送方根据序列号重传 | 接收方发送ACK(含期望序列号),发送方根据序列号判断并重传 |
协议复杂度 | 相对简单,但可能因ACK/NAK出错导致问题 | 增加序列号处理,复杂度提升 | 去除NAK,通过ACK和序列号处理,复杂度适中 |
错误处理 | 依赖于NAK的明确指示 | 更加灵活,通过序列号可准确判断出错分组 | 最灵活,发送方可根据ACK中的序列号判断并重传任意分组 |
冗余处理 | 无特别处理冗余ACK/NAK的机制 | 可能出现冗余ACK/NAK,但序列号帮助区分 | 通过序列号避免冗余ACK导致的混淆 |
接收方状态 | 无需记住期望接收的分组序号 | 必须记住期望接收的分组序号(通过状态机) | 同RDT2.1,但无需处理NAK状态 |
这个表格清晰地展示了RDT2.0、RDT2.1和RDT2.2在信道特性、差错检测、确认机制、重传机制、协议复杂度、错误处理、冗余处理以及接收方状态等方面的区别。通过这些对比,可以更好地理解这三种协议的特点、差异以及它们之间的演进关系。
- 以下是将上述关于RDT2.0、RDT2.1和RDT2.2之间更新差异的内容以表格形式呈现的结果:
RDT2.0 | RDT2.1 | RDT2.2 | |
---|---|---|---|
错误处理改进 | 引入差错检测机制,使用校验和检测比特差错。接收方发现错误发送NAK,发送方重传。但未考虑ACK/NAK可能出错。 | 引入序列号,发送方在每个分组中加入序列号,接收方根据序列号判断分组是否重复或按序到达。即使ACK/NAK出错,也能准确判断哪些分组需重传。 | 去除NAK消息,接收方只发送ACK,并在ACK中包含期望接收的下一个分组的序列号。发送方根据重复的ACK判断需重传的分组。 |
协议复杂度提升 | 无需处理序列号,协议相对简单。 | 引入序列号,发送方和接收方需处理序列号,协议复杂度提升。 | 去除NAK,但接收方需在ACK中包含序列号,发送方需根据序列号判断重传,协议复杂度与RDT2.1相当,但逻辑更简洁。 |
接收方状态管理变化 | 不需要记住期望接收的分组序号,因为信道被假设为只会出现比特差错。 | 必须记住期望接收的分组序号,以处理分组丢失和乱序到达的情况。接收方根据序列号判断分组是否接受。 | 同RDT2.1,必须记住期望接收的分组序号,并根据序列号判断分组是否接受。但逻辑上更侧重于通过ACK中的序列号进行状态管理和错误恢复。 |
这个表格清晰地展示了RDT2.0、RDT2.1和RDT2.2在错误处理改进、协议复杂度提升以及接收方状态管理变化方面的差异。每个版本的特点和变化都通过简洁明了的语言和逻辑分明的结构进行了呈现。
(五)经具有比特差错的丢包信道可靠数据传输:rdt3.0
现在假定除了比特受损外,底层信道还会丢包,这在今天的计算机网络中并不罕见。协议现在必须处理另外两个关注的问题:怎样检测丢包以及发生丢包后该做些什么。在rdt2.2中已经研发的技术,如使用检验和、序号、ACK分组和重传等,使我们能给出后一个问题的答案。为解决第一个关注的问题,还需增加一种新的协议机制。
我们让发送方负责检测和恢复丢包工作。假定发送方传输一个数据分组,该分组或者接收方对该分组的ACK发生了丢失。在这两种情况下,发送方都收不到应当到来的接收方的响应。如果发送方愿意等待足够长的时间以便确定分组已丢失,则它只需重传该数据分组即可。但是发送方需要等待多久才能确定已丢失了某些东西呢?很明显发送方至少需要等待,发送方与接收方之间的一个往返时延加上接收方处理一个分组所需的时间。在很多网络中,最坏情况下的最大时延是很难估算的、确定的因素非常少。理想的协议应尽可能快地从丢包中恢复出来。
从发送方的观点来看,重传是一种万能灵药。发送方不知道是一个数据分组丢失,还是一个ACK丢失,或者只是该分组或ACK过度延时。在所有这些情况下,动作是同样的:重传。为了实现基于时间的重传机制,需要一个倒计数定时器(countdown timer),在一个给定的时间量过期后,可中断发送方。因此,发送方需要能做到:①每次发送一个分组(包括第一次分组和重传分组)时,便启动一个定时器。②响应定时器中断(采取适当的动作)。③终止定时器。