为上层实体提供的服务:数据可以通过一条可靠的信道进行传输。借助可靠信道,传输数据比特不会受到损坏或者丢失,而且所有数据都是按照其发送顺序进行交付。
那么,可靠传输协议的责任就是实现抽象服务,但是下层确是不可靠的,这种TCP在不可靠的IP端到端网络层之上实现的可靠传输协议。
在课本中,我们仅仅考虑单向数据传输,
假设底层是可靠的,那么此时的协议就会变得简单,这个协议就是rdt1.0。
有限状态机FSM:发送方和接收方各有一个自己的FSM,横线上方是引起状态变迁的事件,下方是状态变迁执行的动作,虚线指向初始状态,如图所示是rdt1.0的FSM。
1)差错检测
需要一种机制来让接收方检测何时出现了比特差错。这些目的都需要额外比特来实现,这些比特会放在rdt2.0的分组校验和字段中。
2)接收方反馈
正如前文说的,发送方和接收方通常在不同的端系统中,所以他们之间需要一个信号来让发送方了解到接收方的情况,就类似前面提到的ACK和NAK。理论上,我只需要1比特,1表示ACK,0表示NAK。
3)重传
接收方收到有差错的分组时,发送方将重新传送该分组。在rdt2.0中,发送方a)有两个状态,一个是等待上层的数据状态,一个是等待接收方的确认状态。那么,状态变迁就不像1.0里面的只会指向自己了。在等待数据的时候,当收到数据rdt_send(data)的时候,就会触发状态变迁,变成等待接收方反馈的状态,在这个过程中,发送方需要将数据打包(sndpkt=make_pkt(data,checknum))并且发送出去(udt_send(sndpkt))。在等待接收方反馈的时候,有两种情况,一种是接收到ACK的反馈信号,一种是接收到NAK的反馈信号。
①在接收到ACK的反馈信号的时候,也就是收到一个分组并且是ACK的时候(rdt_rcv(rcvpkt)&&isACK(rcvpkt)),就会转换成等待上层调用的状态了。在图中要注意的是,在FSM中,如果动作没有的话,要用A来代替。
②当接收到NAK反馈的时候,也就是接收到分组并且是NAK的时候(rdt_rcv(rcvpkt)&&isNAK(rcvpkt)),发送方会重传数据(udt_send(snapkt)),直到收到ACK的反馈,才会进入下一个数据的发送。因此,rdt2.0也被称为“停等”协议。在b)里面描述的是接收方。跟1.0一样的是,这里面的接收方仍然只有一个状态,所以状态变迁之后还会指向自己。对于接收方而言,“确认信号”就是自己所确定的接收到的数据是正确的还是不正确的。
①当接收到的数据是正确的时候,也就是rdt_rcv(rcvpkt)&¬corrupt(rcvpkt)来引起的状态变迁。同时,会从收到的分组中处理获取数据extract(rcvpkt,data),同时将数据传给高层deliver_data(data),并且给发送方反馈一个ACK(sndpkt=make_okt(ACK)和udt_send(sndpkt))。然后自己继续处于等待接收下层调用的状态。
②当接收到错误信息的时候,也就是rdt_rcv(rcvpkt)&&corrupt(rcvpkt),是引起状态变迁的事件。同时,会将NAK反馈给发送方(sndpkt=make_pkt(NAK)和udt_send(sndpkt)),然后继续处于等待下层调用的状态,此时等待的就是重传过来的结果了。
可是,如何确保ACK和NAK没有出错呢?这个时候,我们就需要让发送方对数据分组进行编号,这样的话,接收方只需要从序号就可以看出来是不是需要进行重传,也可以辨别是新的分组还是重传的分组了。 4.1.2.1rdt2.1
在rdt2.1中就是对2.0的修订,增加了分组序号,并且发送方和接收方FSM的状态数都是之前的二倍,这是因为协议状态此时必须反映出目前正发送的分组或者接收方希望接收的分组的序号是0还是1。
如图所示是rdt2.1里面的发送方,虚线指向的是初始状态。
如上图所示是rdt2.1里面的接收方FSM。其中的两个状态跟发送方里面的0和1是一一对应的。按照顺序,初始状态是要等待0分组的。在rdt2.1里面,接收方如果收到正确也就是按序的正确分组,就会反馈一个ACK,如果收到的不正确的或者是乱序的,就会反馈一个NAK或者不进行反馈。但同时,如果接收方反馈了一个上面已经ACK过的序号ACK,那么也表明接收出错了。在初始等待下层的0的时候,会有三种情况:
①收到的数据是错误的:
也就是rdt_rcv(rcvpkt)&&corrupt(rcvpkt),这个时候会执行一些动作:也就是将NAK与检测字段一起打包发送回去,然后继续等待下层对0的数据。
②收到的数据是正确的但是不是所想要的序号:
也就是rdt_rcv(rcvpkt)&¬corrupt(rcvpkt)&&has_seq1(rcvpkt),这个时候,接收方也会进行反馈ACK。
③收到的数据是正确的并且序号也是正确的:
也就是rdt_rcv(rcvpkt)&¬corrupt(rcvpkt)&&has_seq0(rcvpkt),这个时候才是正确的情况,接收方会从收到的分组里面处理得到数据,再将数据发送给底层,同时对发送方进行ACK反馈。
对于分组1的情况是一样的。 4.1.2.2rdt2.2
2.2里面增加了接收方ACK里面的序号,也就是ACK是由区别的,对0的ACK和对1的ACK是不一样的。
如图所示是rdt2.2里面的发送方FSM。跟2.1相比,就是在isACK的参数里面,多了序号0或者1,通过这些序号来确认这个ACK是对哪一个分组的。
如图是rdt2.2接收方的FSM。在发送ACK的时候,也会加上序号参数。比如sndpkt(ACK,1,checknum)里面就是有ACK对应的序号1,说明这个ACK是针对序号1的确认。而且没有NAK的返回。这样的话,当发送方收到的ACK不是对应的序号的ACK的时候就会进行重传。
在底层信道传输中,丢包也是很常见的情况。那么怎样检测丢包?丢包之后还要做什么?
丢包之后,我们可以进行重传。但是要想检测丢包我们需要引入新的机制rdt3.0,也称为比特交替技术。
通过倒计数定时器来解决丢包的检测问题。针对发送方而言,确定好一个合适的时间范围,在时间范围内,如果没有收到对应的ACK,那么就认为已经丢包了,就要对这个分组进行重传。那么即使这个分组没有丢包,也可以通过2.2里面的序号来解决冗余数据分组问题。
如图是3.0里面会出现的一些情况:正常无丢包、分组丢失、ACK丢失以及过早超时。
rdt3.0是一个停等协议,也就是只有等到上一个分组的ACK之后才会进行下一个分组的发送。这样的话,对于发送方而言,发送方真正忙于发送的时间占整个时间只有很小一部分,其他时间都在发送分组和等待ACK。这样的利用率很低。
如图就是停等操作和流水线操作的对比。流水线操作可以运行发送方发送多个分组而不用等待确认。这样的话,在等待ACK之前,发送的分组就很多,利用率就会提高。
存在的问题:
1)需要增大序号范围:这个时候我们需要的序号就不单单是0和1了,而是需要多个序号进行分组标识。
2)协议的发送端和接收端必须要能缓存多个分组:发送方至少需要缓存已经发送但是没有得到确认的分组,接收端也至少需要缓存已经接收到的分组。
3)如何处理丢失、损坏以及延时过大是个问题。
解决流水线的差错恢复有两种基本方法:回退N步hxdgtx和选择重传SR。
4.3回退N步虽然允许发送方发送多个分组而不等待确认,但是也有最大受限未确认的分组数不能超过N个。
如图所示是hxdgtx里面发送方所看都的分组序号。其中base是未得到确认分组的最小序号,nextseqnum是下一个即将发送的分组序号。所以[0,base-1]就是已经收到确认的分组序号,[base,nextseqnum-1]是已经发送但是还没有接收到确认的分组序号,[nextseqnum,base+N-1]就是即将要发送的分组序号。
如图所示是hxdgtx中的发送方FSM,其中画出了1、2、3、4个部分。
①因为窗口大小是N,所以nextseqnum必须在规定范围内,也就是到base+N-1,这样的话才是有效的。只有当窗口没有满的时候,也就是nextseqnum<base+N的时候,才可以进行数据发送。将数据打包发送之后,定时器要注意计时。如果窗口已经满了,就要拒绝上层传输的数据。
②超时事件:当其中一个分组超时之后,该分组及其之后的分组都要重新传送,相应地,定时器也要重新计时,这和hxdgtx里面的累计确认是有关系的。
③收到ACK:当收到一个ACK之后,因为累计确认,那么该数据里面的num就代表该分组及其之前的分组都已经确认接受了,那么此时base就会变成分组序号的下一个序号,也就是getacknum(rcvpkt)+1。然后判断后面的数据是否要进行发送,来决定是否要开始计时。
④没有收到ACK就要继续等待
如图所示是hxdgtx接收方的FSM。接收方的累计确认是指,当发送n的ACK时,代表分组n及其之前的所有分组都已经正确接受了。
所以,如果接收方收到的分组不是按序的,比如接收方还没有正确收到分组n,但是分组n+1已经来了,那么接收方就会丢掉分组n+1。虽然接收方可以实现重新排序,但是直接丢弃对于接收方而言是最简单省事的一种方法。只是在这种情况下,发送方的任务就会增大。
发送方只会重传那些接收方出错的数据,也就是丢失或者受损的数据分组。这个时候,接收方就没有累计确认了。如果在接收方分组没有按序,那么接收方会进行缓存,知道分组按序之后才会将数据传送出去给上层。在SR里面,发送方和接收方各有各的窗口,他们看到的窗口情况也可能不一致。
但是会存在这样一个问题:新传分组和重传分组可能会混淆。窗口大小为3,但是有四个分组0123,那么,如果第一次的0分组需要进行重传,以及第二次发送分组301里面的0,接收方是无法区别是新传送的数据还是重传的数据。
这就要求窗口长度要小于等于序号空间的一半。