RDT 协议 (可靠数据传输协议)

RDT (reliable data transfer)协议详解

 

零、文档目录

  1. .名词解释

  2. 背景介绍

  3. rdt协议的实现

  4. 总结

  5. 疑问解析

  6. 参考文献

一、名词解释

        rdt协议(reliable data transfer)可靠数据传输协议

二、背景介绍

        计算机网络通过对网络进行分层设计,将一个庞大而复杂的系统,模块化层次化,( 大致分层如图 2.1所示 ) 其中的每个层次为其上层提供特定的服务内容, 并使用来自下层的特定功能, 各个层次中明确了其需要实现的内容, 但并不指明其中具体的实现方式。

                                                        2.1 (五层因特网协议栈)

        运行在应用层中的客户端(服务端)应用程序进程通过套接字将数据推送到运输层。同样地, 有服务端(客户端)进程通过套接字接受来自运输层的数据。对应用层而言, 它所能看到的底层就是一条可靠的信道(如图 2.2 所示)。但是,对于现实中的数据传输,由于受到噪声干扰、网络拥堵等各种影响,难免会出现数据受损、丢包等事故, 而rdt协议就是为了解决这样一个问题,而诞生的。

                                                        2.2 (应用层数据传输简图)

三、rdt协议的实现

        参考《计算机网络自顶向下》这本书,在这里我们从简单到复杂来探讨这个协议的实现过程(要注意的是, 我们在此处探讨的rdt协议是建立在(stop-and-wait)停等协议上的)

        首先, 先将各个部分做好定义和命名初始化处理, 为进一步探讨协议做好准备。

        我们将数据的发送方设定为 sender, 接收方设定为 receptor,发送方和接收方各自维护一个自己FSM(Finite-State-Machine)有限状态机, 用于记录当前的状态。我们用状态来描述发送方和接收方在执行通讯各个时期的特征。

1. rdt1.0

        在rdt1.0中我们只专注于考虑如何实现核心功能,而不去考虑其他异常。因此我们在此处假定两个应用层之间存在着这么一条可靠信道,它可以保证从应用层的一侧到另一侧数据不丢失,因此此时的发送方和接收方只会有一个状态。

发送方: 等待应用程序下发调用指令, 发送数据

接收方: 等待来自下层的调用指令, 接受数据并缓存

数据流动图如3.1 所示

                                                        (3.1 rdt1.0 数据流动图)        

数据流动情况分析

sender

  1. 应用进程调用 rdt_send(data)方法, 将数据推送至运输层
  2. 运输层调用 make_pkt方法, 将源自于应用程序的报文分组打包成报文段
  3. 运输层调用udt_send方法, 将报文段推送至信道

receptor        

  1. 较低层(比运输层低的层次)执行 ret_rev方法,将数据推送到运输层
  2. 运输层调用 extract, 从报文段中提取出数据(可能涉及分组等细节实现, 此处不作探讨)
  3. 运输层调用 deliver_data方法将数据推送至应用层

        此时, 我们就可以简单地实现让数据从sender端传输到receptor端了, 当然此时距离可靠数据传输还有一定的距离。因此, 在rdt2.0及以后的版本我们开始考虑当异常事件发生的时候, 如何保证数据的可靠性

2. rdt2.0

        在rdt 2.0 首先考虑比特差错出现的情况, 比特差错通常会出现在可能受损的物理部件之中, 因此需要引入比特差错矫正的功能。(udp的比特差错校验方法) 在考虑出现比特差错事件发生的 rdt 2.0 中,需要加入 肯定确认(ACK positive acknowledgement)、 否定确认(NAK negative acknowledgement)的情况。 对于否定确认的报文, 需要提示发送方重新发送该数据(数据恢复)。基于这种重传机制的可靠数据传输协议称为 自动重传协议(Automatic Repeat reQuest,ARQ)

于是 Sender 方需要增加一个状态,等待 ACK 或 NAK, 整个rdt 2.0 执行流程如图 3.2 所示

                                                                (3.2 rdt2.0 数据流动图)

数据流动情况分析

Sender 端

  1. 应用层调用rdt_send方法, 将数据推送至应用层
  2. 应用层调用make_pdt将数据打包成报文段, 并在报文段中封装进一个 校验码
  3. 应用层调用udt_send方法将打包完成的报文段推送至信道
  4. Sender端 此时状态迁移为 等待ACK应答 或者 NAK 应答状态

Receptor 端

  1. 较低层通过rdt_rcv方法, 将数据推送到运输层
  2. 运输层接收到报文段, 对报文段数据进行校验处理, 校验成功则执行第4步, 校验失败则跳转到第3步
  3. 发送NAK指令, 继续等待较低层的调用
  4. 发送ACK指令, 继续等待较低层的调用

Sender端

  1. 接收到NAK应答指令执行第2步, 接收到ACK执行第3步
  2. 接收到NAK,Sender直接将打包好的数据再一次通过udt_send方法推送到信道, 保持等待ACK或NAK指令状态
  3. 接受到ACK, Sender端不再阻塞, 可以发送新的数据, 状态迁移为等待上层调用状态

        需要注意的是: 由于在这里讨论的rdt协议采用了停等协议。因此, Sender端在等待ACK 和 NAK 答复指令的时候, 处在阻塞的状态。

3. rdt 2.1

        在rdt 2.0 阶段看起来已经解决了比特受损问题, 但实际上此时的协议仍是有着巨大缺陷的。

        由于数据流动的双向性, 在rdt2.0阶段, 我们无法保证Receptor端发送的答复指令是否发生比特差错情况, 因此很有可能Sender端收到了一个比特受损的应答指令, 为了解决Sender端收到比特差错的异常指令情况, rdt2.1版本在Sender端对数据报进行了编号处理*由于我们此处讨论的 rdt协议是运行在停等协议上的, 因此序列号设定可以以类似于链表一样的形式进行构建,由 previout->current 两个状态交替构成, 即 0 1 0 1 0 1,分别代表上一次以及当前处理的数据)当Sender端校验到来自receptor发送答复指令出现比特差错的时候,Sender端直接重发上次打包的数据。 相较于 rdt 2.0版本, 在receptor端增添了一个冗余分组(duplicate packet)处理, 即维护一个缓存,用于存储当前获取到的数据, 倘若第二次收到的数据为冗余数据(和上次接受到的数据相同),这就表明receptor端发送的回复指令出现了比特差错异常, 因此它可以直接丢弃掉本次收到的数据, 然后再发送一条对缓存数据的ACK确认指令。 而对于非冗余数据, receptor端则是将新数据置换到缓存区中, 随后发送一个对本次数据的ACK确认指令。

rdt 2.1为什么不效仿 rdt 2.0中, 让receptor端去接受一个 ack或 nak, 这是因为可能出现一个比较有趣的问题, 这个问题后续讨论

数据流动如图3.3所示

                                                        (3.3 rdt2.1 数据流动图)

数据流动情况分析

Sender 端

  1. 应用层调用rdt_send方法, 将数据推送至应用层
  2. 应用层调用make_pdt将数据打包成报文段, 并在报文段中封装进一个 校验码和一个值为 0 或 1的序号
  3. 应用层调用udt_send方法将打包完成的报文段推送至信道
  4. Sender端 此时状态迁移为 等待序号为 0 或 1 的报文段 ACK应答 或 NAK 应答状态

Receptor 端

  1. 较低层通过rdt_rcv方法, 将数据推送到运输层
  2. 运输层接收到报文段, 对报文段数据进行校验处理, 校验成功则执行第4步, 校验失败则执行第3步
  3. 发送NAK指令,继续等待下层调用
  4. 检测数据序号, 如果是冗余数据, 直接丢弃数据,发送对缓存栈中数据的ACK指令,对于非冗余数据, 则将数据置换到缓存栈之中, 发送一个确认对本次数据的ACK指令, 继续等待来自下层的调用

Sender端

  1. 1. 接收到应答指令后,进行数据校验处理, 如果数据校验错误, 直接重传上次数据, 如果数据校验正确,则执行第 2步
  2. 2. 判断接受到的应答指令,如果指令为ACK执行第3步, 如果指令为 NAK 执行第4步
  3. 3. 接收到ACK指令, Sender端不再阻塞, 可以发送新的数据 1 或 0, 状态迁移到 等待来自上层调用
  4. 4. 接收到NAK指令, 重传上次数据, 状态迁移到 等待ACK或NAK状态

rdt 2.2

        实际上rdt 2.2 并没有做出更多变更, 它只是在rdt2.1基础上进行了一次逻辑优化, 因为有了序号的存在,receptor端不在需要用NAK指令去表示收到的数据产生了比特错误。 取而代之是, 对于比特受损的数据, receptor端直接丢弃, 并发送一个对缓存区数据的确认ACK(一开始缓存区为空也不要紧,由于序列是010101交替的序列,只要发送一个与当前ACK不同的序号即可),而此时, Sender端也需要维护一个缓存, 用于记录上一次发送的数据, 当接受到的ACK与缓存序列号相同, 那么就表示发送的数据发生了比特差错, 此时重新发送一次缓存区中的数据即可。

数据流动如图 3.4所示

                                                                (3.4 rdt2.2 数据流动图)

数据流动情况分析

Sender 端

  1. 应用层调用rdt_send方法, 将数据推送至应用层
  2. 应用层调用make_pdt将数据打包成报文段, 并在报文段中封装进一个 校验码, 和一个值为0 或 1的序号 , 并将其存储至缓存区
  3. 应用层调用udt_send方法将打包完成的报文段推送至信道
  4. Sender端 此时状态迁移为 等待序号为 0 或 1 的报文段 的ACK或NAK应答状态

Repetor 端

  1. 较低层通过rdt_rcv方法, 将数据推送到运输层
  2. 运输层接收到报文段, 对报文段数据进行校验处理, 校验成功则执行第4步, 校验失败则执行第3步
  3. 发送缓存区数据序号的确认ACK指令,保持等待下层调用的状态
  4. 检测本次数据序号, 如果是冗余数据, 直接丢弃数据,发送缓存栈中数据序号的ACK指令,对于非冗余数据, 将数据置换到缓存区之中,并发送一个对本次数据序号的确认ACK指令

Sender端

  1. 接收到应答指令后进行数据校验处理, 如果数据校验错误, 直接重新发送上次数据, 如果正确则执行第 2 步
  2. 判断接受到的应答指令,如果指令为ACK中的序号等于缓存区的序号,执行第 3 步, 如果ACK指令序号不等于缓存区序号则执行 第4 步
  3. 重新发送上次数据, 状态迁移到 等待ACK应答指令
  4. Sender端不再阻塞, 可以发送新的序号为 1 或者 0 的数据, 状态迁移到 等待来自上层调用

rdt 3.0

        在处理好了比特差错的问题之后, 需要考虑的就是来自底层信道传输的另一个问题, 丢包异常( 即发送方或者接受方由于网络阻塞等状况,并没有收到来自于对方的应答数据, 在现实中丢包现象是非常常见的), 因此, 我们可以加入一个定时器来处理丢包现象, 当发送一个报文段的时候, 就开启一个定时器, 在定时器结束期间, 如果没有收到对应数据的应答报文, 则重传数据。

数据流动分析

Sender 端

  1. 应用层调用rdt_send方法, 将数据推送至应用层
  2. 应用层调用make_pdt将数据打包成报文段, 并在报文段中封装进一个 校验码, 和一个值为0 或 1的序号 , 并将其存储至缓存区
  3. 应用层调用udt_send方法将打包完成的报文段推送至信道, 并启动一个定时器事件
  4. Sender端 此时状态迁移为 等待序号为 0 或 1 报文段 的ACK应答状态
  5.  倘若在定时器等待时间内, 没有收到响应, 则重新执行第3步

Receptor端

  1. 较低层通过rdt_rcv方法, 将数据推送到运输层
  2. 运输层接收到报文段, 对报文段数据进行校验处理, 校验成功则执行第 4 步, 校验失败则执行第 3 步
  3. 发送缓存区数据序号的确认ACK指令, 同时开启一个定时器, 保持等待下层调用的状态
  4. 检测数据序号, 如果是冗余数据, 直接丢弃数据,发送缓存区中数据序号的ACK指令,对于非冗余数据, 将数据置换到缓存区中, 发送一个对该数据序号的确认ACK指令, 同时开启一个定时器
  5. 倘若在定时器等待时间内, 没有收到响应, 则重新执行第3或第4步

Sender端

  1. 接收到应答指令后进行数据校验处理, 如果数据校验错误, 直接重新发送上次数据, 如果正确则执行步第 2 步
  2. 判断接受到的应答指令,如果指令为ACK中的序号等于缓存区的序号,执行第 3 步, 如果ACK指令序号不等于缓存区序号则执行 4
  3. 重新发送上次数据, 状态迁移到 等待ACK, 同时开启一个定时器
  4. Sender端不再阻塞, 可以发送新的序号值为 1 或 0 的数据, 状态迁移到 等待来自上层调用
  5. 倘若在定时器等待时间内, 没有收到响应, 则重新执行第 3或 第4步

四、总结

        至此,在rdt3.0我们已经得到了一个可靠的数据传输协议,当然它只是一个抽象的雏形, 建立于停等协议之上, 还需要不少细致的优化, 让其更好的切合现实, 比如如何脱离停等协议实现更高的传输率, 以及拥塞控制等等。文章内的方法为了保证准确性, 表达式大多参照于《计算机网络自顶向下方法》 书籍中的内容, 但是为了简化一下表达, 也做出了一点变更。

五、疑问解析

1.如何确认数据发生比特受损现象

参照UDP协议中的检验和校验方式,在这里举一个简单的例子(UDP中的校验方式, udp通过校验和的方式来实现差错检验功能

        例如, 发送了两个16bit的报文:

                  0101 0101 0101 1111  

                  1111 1111 0000 1111

   求和有 1 0101 0100 0000 1110

       回卷    0101 0100 0000 1111 (回卷即把溢出部分的数据重新进行逻辑加运算到序列尾部)

         取反 1010 1011 1111 0000

        在发送时数据中会添加如下一个字段, 其值为经过上述运算后的一串序列

        检验和:1010 1011 1111 0000

        当接收方, 收到来自底层的数据时, 会开始进行检验, 首先将应用数据中所有数据行进行求和运算得到一个和序列,将和序列与检验和做一次逻辑加运输, 得到的预期结果应该为 1111 1111 1111 1111,倘若不是,则表示本次数据发生了比特差错异常。

2.为什么rdt2.1 不采用 rdt2.0的方法, 让发送端给接收端发送ACK和NAK信息呢?

我们可以考虑如下这么一个例子:

        Sender端发送了一个报文段给Receptor端, 于是Receptor端回复了一个ACK给Sender, 但不幸的是 Sender端得到的ACK比特受损, 于是 Sender端发送了NAK, 此刻Receptor端得到了NAK, 又发送了一个ACK, Sender端口得到了这个ACK。此时的Sender端无法判断这个ACK是对于第一次数据发送成功的应答ACK还是来自自己NAK指令的应答ACK, 这使得指令数据具有了二义性, 这是一种不好的设计形式。

        不止是以上这种特殊情况, 由于丢包和比特受损情况, 我们有时不得不传输许多不携带具体内容的ACK/NAK数据, 这是一种对数据的额外开销,也容易造成信道阻塞。

六、参考文献

1.《计算机网络自顶向下方法》

2.《图解TCP/IP》

  • 17
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值