可靠数据传递原理
可靠数据传递的重要性
可靠数据传递rdt这件事在网络传输的应用层,网络层以及链路层都极为重要,因为信道的不可靠,导致了rdt的复杂程度极高。
rdt的实现基于下层所提供的udt(不可靠数据传输)
rdt要做的事情
rdt_send()被上层调用,如应用层。将数据交付给我们,我们基于下层的不可靠数据传输提供的服务---> udt_send() 来实现可靠数据传输,保证数据不丢失,不重复,而且保序
图中另一侧对应了相同层级的解包。
本文讲解步骤
-
我们将通过对rdt1.0 ~~~ rdt3.x的理论实现与逐步改进,详细了解rdt的前世今生。
-
我们只讲解单方向的数据传输,(双向数据传输其实就是两个单向数据传输)
rdt1.0的实现
在这一步,我们通过对信息打包实现数据的不出错,不丢失。
- 没有比特丢失
- 没有分组丢失
在函数层面的体现
发送方体现如下
rdt_send(data) -> {
package= make_pkt(data)
udt_send(package)
}
打包然后发送出去,其重点在于打包
接收方体现如下
rdt_rcv(packet) ->
extract(packet,data)
deliver_data(data)
其重点在于解包与解析
rdt2.0的实现
面临的问题,下层信道可能出错导致分组中的比特翻转。
解决的办法:
-
在rdt2.0中我们引入了编码的检错机制
1.发送方进行差错控制编码,缓存。 2.接收方进行校验,确认编码是否正确
-
接收方向发送方恢复控制报文(ACK,NCK)来告知发送方收到报文的好坏
-
发送方收到NCK报文,将刚刚发送的数据重新发送给接收方。(收到ACK则发送下一个数据包)
代码层面
rtd2.1的出现
rtd2.0有一个致命的错误,因为下层提供的服务是无法保证消息不出错的,一旦ACK或者NCK在传输过程中出错,则发送方无法知道接收方是否收到消息。所以在2.1版本中,发送方一旦收到无法解析的包,则会重新发送data包,尝试重新建立连接
代码层面的
在该模型中,接收方在确认 收到消息&&消息没有出错&&确认是seq0 后,将信息解包发给上层,同时向发送方返回ACK包,确认消息收到,提示发送下一条消息。
关于模型的讨论
在上面的案例中,我们会发现一个问题,接收端需要知道它收到的消息是seq0号消息还是seq1号消息。一开始我比较困惑为什么要引入序号,因为该消息模型属于stopAndWait模型(停止并等待),似乎在该模型中是不必要的。后来发现是为了防止消息重复,试想这样一个场景,接收方收到sq0,并返回ACK,但是ACK在信道内丢失,则当发送方触发超时重发机制重新发送sq0,此时问题出现,接收方误以为发送方已经收到了ACK,并且返回的是sq2,在没有消息确认序号的情况下,出现问题。
另一个场景则是ACK在传输中超时,触发超时重传,返回2个ACK,同样会产生问题
这里的问题在于,我认为超时重传机制在rdt3.0中出现,在rtd2.1中没有超时重传,所以不会发生超时重传。其实rdt2.1是有很大问题且被废弃不使用的协议,关于序列号的引入,在Stop-and-Wait是必要的,该模型不会发生大批量的消息传输,所以只需要1比特作为序列号区分消息即可,即 0或1。
在rdt2.1中其实已经引入了序列号,虽然没能完全解决消息丢失问题。
rdt2.2的出现
rdt2.2解决了一个问题NAK free
不再需要NAK,而是通过再次回复对上一个消息的确认来表示现在收到的消息有问题
设发送:P1 P2 P3 P4。 P1已发送并成功接收,P2出现问题
- 接收方:再次回复P1已确认
- 发送方:已知:P1,P2均已发送; P1已确认收到2次; P2已确认未收到
- 分析解决:说明P2出现问题,发送方重发P2。
rdt3.0的出现
当一台主机经过互联网连接到另一台主机,中间有大量的路由器和交换机,就可能发生信息丢失。此时超时重传机制就显得非常必要了。
在rdt3.0中引入了超时重传机制,在消息发送时启动,超时则重发。
这里会有一个值得探讨的问题,即超时定时间的设置。
- 当主机只有2台,两者之间的延时是可计算的,则我们只需要将超时时间设置成比传输时间稍微长一点即可,就能保证不会发生正常消息未到达,就触发超时的问题。
- 当主机有多台,发生1对多时,问题就会比较复杂,一般来讲会根据延时的正态分布来设定超时时间,设置在多数消息能正常往返的曲线峰值后即可。
代码层面
此时我们发现,我们已经完全解决了消息不可靠的问题,对于消息编号的确认,内容的确认,对错误消息,重复消息的重发机制。数据传输变得可靠,不丢失,不重复,不失序。
rdt3.0 中 stopAndWait协议的问题
好了,在上一步中,我们完善了我们协议的可靠性。但同时我们发现,我们的协议在使用过程中的利用率非常低。想象一下,如果每次只能发送一条数据包。而且需要等待消息抵达并收到回复后才能发送下一条,这会是很大的问题。 我们尝试计算一下该协议的利用率。 设我们每次发送的数据大小为1kb,在1Gbps的带宽下,发送方与接收方的延时为15ms。则我们的利用率为:
首先我们计算出当前带宽下,发送出1kb消息所需的时间:
因为我们的消息包每个大小为固定的8kb,无法分成1kb发送,所以发送1kb与发送8kb所需的时间是一致的,即8微秒(我们上面计算出的是实际情况,也是非理想情况下所需的时间)。
则Sender的利用率为 用于发送的时间/延时+用于发送时间
注意,上面用于求sender的利用率时排除了接收者准备所需要消耗的时间。
那么对于1Gbps的数据来说,我们按照当前的利用率,相当于只使用了1Gbps x 0.00027=270kbps,也就是270*0.125=33.75kb/s.
rdt3.0 协议的增强(pipeline协议的出现)
我们如何解决可靠数据传输的低效问题,从上面可以看出,我们数据传输低效的症结在于协议,我们尝试通过改变协议,一次性发多个分组(数据包),提升我们分组的效率 假设我们一次性发送3个分组,则效率提升如图
回想一下,我们在rdt2.2加上了分组编号,而当时只有0或者1,而此时我们只需要增加编号,就可以解决多个分组的保序与重复问题。
此时我们的瓶颈就不再是协议了,而是带宽本身了。
pipelined协议(流水线协议)
对于允许发送多个未知分组的协议,我们称为pipelined协议。而流水线协议又分为2种。
- GBN协议(goback N)
- sr协议(选择重传协议 Selective Repeat)
通用的 slide window协议
**slide window协议包括 **
- 停止等待协议
- gbn协议 他们的共同点接收窗口都为1,区别是他们的发送窗口大小(s w),对于停止等待协议,无需多收,他的发送窗口大小肯定是1,gbn则反之。
除了对于上面所说的需要多个bit位用于编号外,我们还需要给发送方和接收方设置缓冲区其目的是为了防止信息发送与接收的速率不一致,避免信息丢失。
- 缓冲区本身是内存中的一个区域,落入缓冲区的分组可以发送
- 发送窗口用来存放那些已发送但未经确认的分组
- 缓冲区是必要的,需要时可以重发
发送缓冲区的大小:他可以一次性发送多个未经确认的分组。
- 对于停止等待协议,他的缓冲区大小=1
- 流水线协议的缓冲区大小>1
缓冲区内部的存放内容
- 未发送的分组。
- 未经确认的分组,确认后才可以删掉
发送缓冲区与发送窗口的关系
- 发送窗口是发送缓冲区的子集,只存放已发送未确认的分组
- 发送缓冲区则包含了待发送的分组,一旦发送窗口前方被解放腾开位置,则发送缓冲区向后挪动,可以发送新的分组出去。
注意:如果0被确认,则往后挪动为12345。
GBN协议的特点在于接收方只有一个接收缓冲区,也就意味着在发送时,即使发送了12345,如果1没有等得到确认出现问题,则12345全部作废!而如果12确认,3未确认,则345也需要重新发送,这就是gobackN协议的含义
sr协议
对于接收缓冲区>1的协议,只有sr协议。
- sr协议可以乱序接收
- sr协议需要多个超时定时器,发送方每一次发送,都需要启动1个超时定时器,而接收方也可以随时发送对应的ACK,发送方收到后就关闭定时器。
- sr协议的滑动窗口也不可以随意滑动,只有前面的都确认了,才可以滑动,也就是说,即使1234都已经确认,如果0号没有确认,那么依旧需要等待0号的确认,超时则重发,直到0号被确认,直接滑动到56789。
3种协议的总结
gbn发送方的代码模型
gbn接收方的代码模型
理解思路后,代码模型的设计并没有什么难度,作者后续也就不再介绍了。