聪明出于勤奋,天才在于积累。——华罗庚
文章目录
- 对协议的进一步改进
- rdt2.1
- rdt2.2
- rdt3.0:含有比特差错和丢包的可靠数据传输协议
- 流水线协议
- 回退n步(GBN)
对协议的进一步改进
rdt2.1
在上一篇文章中,我们讲到对于产生比特差错的信道,我们引入了ACK和NAK报文进行对发送数据准确性的确认。但另一个问题随之产生:如果ACK和NAK报文也受损了怎么办呢?
我们通过将报文打上标识来解决这个问题。由于在实际过程中,我们只需要辨别当前要发送的分组和它的上一个分组(ACK或NAK报文出问题的分组),因此我们只需要将0和1两个标识反复使用就可以了。例如,当前分组标识是0,那么下一个分组标识为1。注意,此时ACK和NAK报文指示的就是发送方最近发送的分组,因此无需对他们附加标识,这一点如果不理解的话可以自己推一下这个过程。
打上标识后的协议称为rdt2.1,下面是rdt2.1的FSM:
在发送方中:
- 当上层调用0时,将分组封装后进行运输,注意在封装时多了一个0作为标记。
- 处于等待对分组0的NAK或ACK报文状态时,如果接收到了响应报文,但报文受损或报文内容为NAK,则一致视为NAK报文,进行重传处理;如果确定响应报文是ACK报文,那么确认分组0成功发送,开始等待发送分组1。
- 发送分组1并处理响应报文的方式与分组0相同
在接收方中,这个过程就要相对复杂一些:
- 处于等待报文0的状态时,接收到来自下层的报文后,如果接收到的报文损坏,发送NAK报文,继续等待报文0;如果接收到的是报文1,即上一个报文,那么发送ACK报文,告知发送方报文1已经收到,继续等待报文0;如果接收到了正确的报文0,那么接收报文0,发送ACK报文,转换到等待报文1的状态
- 等待报文1的状态处理和报文0相同。 、
注意,在rdt2.1中,接收方给发送方发送的ACK报文,其所指的是发送方最后一次发送的报文内容没有问题已经收到,至于是不是接收方想要的报文,是不一定的。
rdt2.2
我们可以用一种巧妙的方式省略NAK。要做到这一点,我们在要使用NAK时再次向发送方发送上一个正确分组的ACK,即和上一次向发送方发送的完全一样的报文,连续收到两个一样的ACK报文时,发送方会知道新发送的报文发生了故障,没有被正确接收。为了区分上一个分组和新分组的ACK报文,我们将ACK报文也打上标识,称为ACK0和ACK1。
在实现方面,它和rdt2.1没有太大区别,只需要将ACK和NAK报文换成ACK0或ACK1,在发送方使用的isACK函数中添加ACK报文的01标识作为参数以指明是哪一个分组的ACK报文:
rdt3.0:含有比特差错和丢包的可靠数据传输协议
含有丢包意味着分组压根没有运输到接收方,这个时候接收方不会向发送方发送任何相应报文,由于到这里我们的rdt协议还是一个停等协议,在转换条件不满足前不会转换状态,因此我们需要新增转换条件来应对丢包的情况。
我们新增一个倒计数定时器,当发送方每发送一个报文时,它启动一个定时器,收到响应报文时,它停止定时器;如果定时器超时,判定这个报文丢包,重传该报文并为重传的报文重启计时器。加入了定时器的协议被称为rdt3.0,也被称为比特交替协议。其发送方FSM如下:
其中start_timer指重启计时器,stop_timer指停止计时器,timeout指超时。
流水线协议
在停等协议中,由于发送方和接收方在完成状态转换后都只能等着,在此期间不做任何事情,这大大制约了发送速率。为了解决这一问题,我们引入流水线协议,即发送方一次发送多个报文而无需等待来自接收方的响应:
我们采用回退n步和选择重传两种机制来实现这个协议。
回退n步(GBN)
发送方的分组序号分为4部分:已经发送且确认收到的分组序号,已经发送但还未被确认的分组序号,还未被上层使用的但处于可用状态(即窗口内)的分组序号,不可用的分组序号。
其中基序号是当前窗口的第一个序号,下一个序号是当下一个分组被传输时将使用的序号,窗口长度是发送方一次最多可以不被确认的分组数量,此时ACK报文标记范围是[0,n-1]。
GBN协议也被称为滑动窗口协议,因为它每次收到ACK报文时窗口都会向前滑动一段距离。另外,在这个协议中,我们将计时器用于未确认的第一个分组,因为这个分组相对其他等待确认的分组来说发送时间最长。
下面是这个协议的FSM:
在发送方中:
- 起始时将基序号和下一个序号都设为1
- 发送数据时,如果下一个序号在窗口内(窗口还没满),则封装并发送数据;如果基序号等于下一个序号(说明这是还没得到确认的第一个分组),则为这个分组启动计时器。最后在任何条件下都要将下一个序号向后挪一个。如果窗口已满,直接拒绝发送数据。
- 超时时,重传未得到确认的所有报文并重启计时器。
- 收到ACK报文时,将窗口向右滑动,使基序号位于ACK报文所确认分组的下一个位置;如果基序号等于下一个序号(没有未确认的分组),停止计时器,反之重启计时器。
在GBN协议中,有些功能是由接收方提供的,因此如果你有些地方不理解,不妨先看下面的接收方FSM:
在接收方中:
- expectedseqnum指接收方期待得到的分组,hassequam()函数通过检验序号检验分组是否是期待的那个。在GBN协议中有一个类似于递推的机制,当分组收到了分组n-1并确认n-1是它期待的分组,那么它期待的分组将变为n,否则期待分组不变。这个机制可以判断分组是否按序发送。
- 起始状态下,期待得到的分组序号为1并发送一个1之前的分组0已经确认的ACK报文。
- 当收到准确且是所期待的分组时,接收方接收该分组,发送ACK报文,将所期待分组序号向后移一位。例如,拥有了n-1,并收到所期待的分组n时,新的期待分组会变为n+1.
- 其他所有情况,重传上一次的ACK报文。
正如我们所说,当发送方收到一个ACK报文时,代表接收方确认在该报文所响应的分组之前都已按序发送,因此可以认为该分组之前的分组也已经被确认(可能他们的ACK报文发生丢包),因此直接将窗口滑动到确认分组的下一位是合理的。
如果一个失序分组到达了接收方,接收方会选择直接丢弃。这是因为如果不这样做,则需要缓存失序分组,得不偿失。
在实际编程GBN协议时,我们也会根据这些函数实现功能的过程进行编写,这种思想被称为基于事件的编程。
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!