13、滑动窗口协议(数据链路层)

1、滑动窗口协议

引言

  • 在之前的协议中,数据帧只在一个方向上传输。而在大多数实际环境中,往往需要在两个方向上同时传输数据,一种做法是使用一条链路来实现双向传输,从机器A到机器B的数据帧可以与从机器A到机器B的确认帧混合在一起,接收方只需要检查入境帧头部的kind字段,就可以区分数据帧与确认帧。
  • 可以有一个更好的办法:当到达一个数据帧时,接收方并不是立即发送一个单独的控制帧,而是等待网络层传递一个数据包给它,然后确认信息被附加在往外发送的数据帧上(使用帧头的ack字段)。实际上,确认信息搭了下一个出境数据帧的便车,这种技术称为捎带确认。使用稍待确认的最主要的好处是更好地利用了信道的可用带宽,帧头的ack字段值占用了很少几位,而单独的一个确认帧需要一个帧头、确认信息和校检和,在下面的协议中,捎带字段只占用帧头中的1位。
  • 然而捎带确认法也引入了一个在单独确认中不曾出现过的复杂问题。为了捎带一个确认,数据链路层应该等待网络层传递给它下一个数据包,要等多长时间?如果时间超过了发送方的超时时间间隔,那么该帧将会被重传,从而违背了确认机制的本意。所以数据链路层必须采用一种措施,比如等待一个固定毫秒数,如果一个新的数据包很快到来,那么确认就可立即被捎带回去,否则必须发送一个单独确认帧。
  • 接下来的3个协议都是双向协议,它们同属于一类称为滑动窗口的协议。这3个协议在效率、复杂性和缓冲区需求等各个方面有所不同。如同所有的滑动窗口协议一样,在这3个协议中,任何一个出境帧都包含了一个序号,范围从0从到某个最大值,用一个n位的字段表示序号,所以序号的最大值通常是2n-1。停-等式滑动窗口协议使用n=1,限制了序号只能是0和1,但更加复杂的协议可以使用任意的n。
  • 所有的滑动窗口协议的本质是在任何时刻发送方总是维持着一组序号,分别对应于允许它发送的帧,我们称这些帧落在发送窗口呢。类似地,接收方也维持着一个接收窗口,对应于一组它允许接收的帧。发送方的窗口和接收方的窗口不必有同样的上下界,甚至也不必有同样的大小,但数据链路层协议将数据包递交给网络层的次序必须与发送机器上数据包从网络层被传递给数据链路层的次序相同,且这些帧必须按顺序递交。
  • 发送方窗口内的序号代表了那些可以被发送的帧,或者那些已经被发送单还没有被确认的帧。任何时候当有新的数据包从网络层到来时,它被赋予窗口中的下一个序号,并且窗口的上边界前移一格。当收到一个确认时,窗口的下边界也前移一格。按照这种方式发送窗口持续地维持了一系列未被确认的帧。(图a,初始化;图b,第一帧发出后;图c,第一帧被接收后;图d,第一个确认被接收后)在这里插入图片描述
  • 由于当前在发送窗口内的帧有可能在传输过程中丢失或损坏,所以发送方必须在内存中保存这些帧,以便满足重传的需要。因此,如果最大的窗口尺寸为n,则发送方需要n个缓冲区来存放未被确认的帧。如果窗口在某个时候到达了它的最大尺寸,则发送方的数据链路必须强行关闭网络层,直到有一个缓冲区空闲出来为止。
  • 接收方数据链路层的窗口对应于它可以接收的帧。任何落在窗口内的帧被放入接收方的缓冲区。当接收到一个帧,而且其序号等于窗口的下边界时,接收方将它传递给网络层,并将整个窗口向前移动1个位置。任何落在窗口外面的帧都将被丢弃。在所有情况下,接收方都要生成一个确认并返回给发送方,以便发送方知道该如何处理。窗口大小为1(上图就是此例)意味着数据链路层只能按顺序接收帧,而网络层总是按照正确的顺序接收数据,跟数据链路层的窗口大小没有关系。

1.1 1位滑动窗口协议

  • 由于发送方在发出一帧后必须等待前一帧的确认到来才能发送下一帧,所以这样的协议使用了停-等式办法。下面代码描述了一个这样的协议,next_frame_to_send指明了发送方试图发送的那一帧,frame_expected指明了接收方等待接收的那一帧,两种情况下,0和1是唯一的选择。
/* Protocol 4 (Sliding window) is bidirectional. */
#define MAX_SEQ 1 /* must be 1 for protocol 4 */
typedef enum {frame arrival, cksum err, timeout} event type;
#include "protocol.h"
void protocol4 (void)
{
seq_nr next_frame_to_send; /* 0 or 1 only */
seq_nr frame_expected; /* 0 or 1 only */
frame r, s; /* scratch variables */
packet buffer; /* current packet being sent */
event_type event;
next_frame_to_send = 0; /* next frame on the outbound stream */
frame_expected = 0; /* frame expected next */
from_network layer(&buffer); /* fetch a packet from the network layer */
s.info = buffer; /* prepare to send the initial frame */
s.seq = next_frame_to_send; /* insert sequence number into frame */
s.ack = 1 − frame_expected; /* piggybacked ack */
to_physical_layer(&s); /* transmit the frame */
start_timer(s.seq); /* start the timer running */
while (true) {
wait_for_event(&event); /* frame arrival, cksum err, or timeout */
if (event == frame_arrival) { /* a frame has arrived undamaged */
from_physical_layer(&r); /* go get it */
if (r.seq == frame_expected) { /* handle inbound frame stream */
to_network_layer(&r.info); /* pass packet to network layer */
inc(frame_expected); /* invert seq number expected next */
}
if (r.ack == next_frame_to_send) { /* handle outbound frame stream */
stop_timer(r.ack); /* turn the timer off */
from_network_layer(&buffer); /* fetch new pkt from network layer */
inc(next_frame_to_send); /* invert sender’s sequence number */
}
}
s.info = buffer; /* construct outbound frame */
s.seq = next_frame_to_send; /* insert sequence number into it */
s.ack = 1 − frame_expected; /* seq number of last received frame */
to_physical_layer(&s); /* transmit a frame */
start_timer(s.seq); /* start the timer running */
}
}
  • 在一般情况下,两个数据链路层中的某一个首先开始,发送第一帧。 换句话说,只有一个数据链路层程序应该在主循环外面包含to_physical_layer和start_timer过程调用。初始启动的机器从它的网络层获取第一个数据包,然后根据该数据包创建一帧,并将它发送出去。当该帧(或者其他任何帧)到达目的地,接收方的数据链路层检查该帧,看它是否为重复帧或者是否损坏,如果是完好的,则传递给网络层,并且接收方的窗口向前滑动。确认字段包含了最后接收到的正确帧的序号,如果该序号与发送方当前试图发送的帧的序号一致,则发送方知道储存在buffer中的帧已经处理完毕,于是它从网络层获取下一个数据包;如果序号不一致,那么重传帧,无论什么时候都要收到一帧,返回一帧。
  • 现在对以上代码实现的协议具体描述。假设计算机A试图将它的0号帧发送给计算机B,同时B也试图将它的0号帧发送给A。假设A发出一帧给B,但是A的超时时间间隔设置得比较短,那么A可能会不停地重发一系列相同的帧,并且所有的这些帧seq=0,以及ack=1。当第一个有效帧到达计算机B,它会接收,并且frame_expected设置为1,而其他后续到达的所有帧都将被拒绝。在每个重复帧到达B后,B都向A发送一帧,其中seq=0和ack=0。最后,总会有一个帧正确到达A,引起A开始发送下一个数据包。
  • 然而,我们应该注意到双方同时发送一个初始数据包时出现的一种极为罕见的情形。图a显示了该协议的正常操作过程,图b显示了这种罕见的情形。如果B在发送自己的帧之前先等待A的第一帧,则整个过程如a所示,每一帧都将被接收。然而,如果A和B同时发起通信,则它们的第一帧就会将交错,数据链路层进入图b描述的状态。在图a中,每一帧到来后都带给网络层一个新的数据包,这里没有任何重复;在图b中,即使没有传输错误,也会有一半的帧是重复的。类似的情形发生在过早超时的情况下,即使有一方明显地首先开始传输也会发生这样的情形。实际上,如果发生多个过早超时,则每一帧都有可能被发送三次或者更多次,严重浪费带宽。(a是正常情况,b是异常情况;记号表示(序号,确认,包号),星号表示接受了一个包)在这里插入图片描述

1.2回退N协议

链路利用率

  • 发送方在发送下一帧之前必须等待前一帧的确认,那么势必浪费带宽。我们可以放松这一限制,这个方案的基本思想是允许发送方在阻塞之前发送w个帧,而不是一个帧,通过选择合适的w值,发送方就可以连续发送帧,因为在发送窗口被重新填满之前前面的确认帧就返回了,因而防止发送方进入阻塞浪费带宽。为了找到一个合适的w值,我们需要知道在一帧从发送方传播到接收方期间信道上能容纳多少个帧。这种容量由比特/秒的带宽乘以单向传送时间所决定,或数据链路层有责任以链路的带宽-延迟乘积序列把数据包传递给网络层。我们可以将这个数量拆分成一帧的比特数,从而用帧的数量来表示,我们将这个数字称为BD。因此,w的值应该设置为2BD+1。如果考虑发送方连续发送帧并且在往返时间内收到一个确认,那么两倍的带宽-延时就是发送方可以连续发送的帧的个数;“+1”是因为必须接收完整个帧之后确认帧才会被发出。
  • 考虑一个50kbps的卫星信道,它的往返传播时延为500毫秒。那么它具有50kbps和250毫秒的单向传输时间,带宽-延迟乘积为12.5kb或12.5个长度为1000位的帧。因此,2BD+1是26帧。假设发送方开始发送0号帧,并且每隔20毫秒发送一个新帧,到t=520时,它已经发送了26帧。这时0号帧的确认刚好返回。此后,每隔20毫秒就会到达一个确认,因此发送方总是可以发送帧。从那时起,25或26个未被确认的帧将始终在旅途中。换言之,发送方的最大窗口尺寸是26。
  • 对于较小尺寸的窗口,链路的利用率将小于100%,因为发送方时常会被阻塞住。我们可以将链路利用率表示成发送方未被阻塞的时间比例:线路利用率≦w/(1+2BD)。这个值是上限,因为它不容许有任何帧的处理时间,并且视确认帧的长度为0(因为它通常很短)。该方程显示无论带宽-延迟乘积多大,发送方的窗口w一定要大。如果延迟很高,发送方将迅速耗尽窗口,即使对于卫星通信那样的中等带宽;如果带宽很高,即使对于普通延迟发送方也将很快耗尽窗口,除非它有一个很大的发送窗口(例如,一个具有1毫秒延迟的1Gbps链路能容纳1Mb)。停-等式协议的w=1,如果延迟传播甚至只有一帧时间,协议效率都将低于50%。

回退n协议

  • 保持多个帧在同时传送的技术是管道化的一个例子。在一个不可靠的通信信道上像管道一样传送帧会引起一些严重的问题:首先,位于某个数据流中间的一个帧将被损坏或丢失,当发送方发现问题时大量的后续帧已经发出,并且到达接收方。当损坏的那个帧到达接收方时,显然它应该丢弃,但接收方该如何处理后续帧呢?
  • 有两种基本办法可用来处理管道化传输中出现的错误,图中显示了这两种技术。(图a,接收方窗口为1时错误的影响;图b,接收方窗口很大时错误的影响)在这里插入图片描述
  • 一种办法称为回退n,接收方只需要简单丢弃所有到达的后续帧,而且针对这些丢弃的帧不必返回确认。这种策略对应于接收窗口大小为1的情形。换言之,除了数据链路层必须要递交给网络层的下一帧以外,它拒绝接收任何帧。如果在计时器超时以前,发送方的窗口已被填满,则管道将变为空闲,最终,发送方将超时,并且按照顺序重传所有未被确认的帧,从那个受损或者被丢弃的帧开始。如果信道错误率很高这种方法将会浪费大量带宽。在图a中,我们可以看到回退n的情形:其中接收方的窗口比较大,0号帧和1号帧被正确地接收和确认;然而2号帧被损失或者丢失。发送方并没有意识到出现了问题,它继续发送后续帧,直到2号帧的计时器超时,然后它退回到2号帧,从这里开始重新发送2号、3号……
  • 针对管道化发送帧发生的错误,另一种通用的处理策略称为选择重传。使用这种策略,接收方将收到的坏帧丢弃,但接受并缓存帧后面的所有好帧。当发送方超时时,它只重传那个最早的未被确认的帧。如果该重传的帧正确到达接收方,接收方就可按序将它缓存的所有帧递交给网络层。选择重传对应的接收方窗口大于1,如果窗口很大,则这种方法对数据链路层的内存需求很大。
  • 选择重传策略通常跟否定策略结合起来一起使用,即当接收方检测到错误(例如,帧的校检和错误或者序号不正确,而不仅仅是丢失帧或者帧损坏),它就发送一个否定确认(NAK)。NAK可以触发该帧的重传操作,而不需要等到相应的计时器超时,因此协议性能得以提高。在图b中,0号帧和1号帧被正确接收,并得到确认;2号帧丢失了。当3号帧到达接收方时,那里的数据的链路层注意到自己错过了一帧,所以它针对错失的2号帧返回一个NAK,但是将第3帧缓存起来。当4号和5号帧到达之后,它们也被缓存起来,而没有传递给网络层。2号帧的NAK抵达发送方后,发送方立即重传2号帧,当该帧到达接收方时,数据链路层将2号帧以及其后的帧按顺序传递到网络层,也可以确认所有这些帧(从2号到5号帧)。如果NAK丢失,则发送方的2号帧计时器最终超时,发送方就会重新发送2号帧(仅仅这一帧),但是这会过去相当长一段时间。
  • 这两种方法恰好是带宽使用率和数据链路层缓存空间之间的权衡。以下代码描述了一个回退n协议的示例。其中数据链路层按序接收入境帧,发生错误之后的所有入境帧都将被丢弃。在这个协议中,我们抛弃了网络层总是有无穷多的数据包要发送的假设。当网络层希望发送一个数据包时,它触发一个network_layer_ready事件。然而,为了使得流量控制机制可以限制任何时候的发送窗口大小或者未确认的出境帧个数,数据链路层必须能够阻止网络层给予它过多的工作,库过程enable_network_layer和disable_network_layer可以完成这样的功能。
/* Protocol 5 (Go-back-n) allows multiple outstanding frames. The sender may transmit up
to MAX SEQ frames without waiting for an ack. In addition, unlike in the previous
protocols, the network layer is not assumed to have a new packet all the time. Instead,
the network layer causes a network layer ready event when there is a packet to send. */
#define MAX_SEQ 7
typedef enum {frame_arrival, cksum_err, timeout, network_layer_ready} event_type;
#include "protocol.h"
static boolean between(seq_nr a, seq_nr b, seq_nr c)
{
/* Return true if a <= b < c circularly; false otherwise. */
	if (((a <= b) && (b < c)) || ((c < a) && (a <= b)) || ((b < c) && (c < a)))
		return(true);
	else
		return(false);
}
static void send_data(seq_nr frame_nr, seq_nr frame_expected, packet buffer[ ])
{
/* Construct and send a data frame. */
	frame s; /* scratch variable */
	s.info = buffer[frame_nr]; /* insert packet into frame */
	s.seq = frame_nr; /* insert sequence number into frame */
	s.ack = (frame_expected + MAX_SEQ) % (MAX_SEQ + 1); /* piggyback ack */
	to_physical_layer(&s); /* transmit the frame */
	start_timer(frame_nr); /* start the timer running */
}
void protocol5(void)
{
	seq_nr next_frame_to_send; /* MAX SEQ > 1; used for outbound stream */
	seq_nr ack_expected; /* oldest frame as yet unacknowledged */
	seq_nr frame_expected; /* next frame expected on inbound stream */
	frame r; /* scratch variable */
	packet buffer[MAX_SEQ + 1]; /* buffers for the outbound stream */
	seq_nr nbuffered; /* number of output buffers currently in use */
	seq_nr i; /* used to index into the buffer array */
	event_type event;
	enable_network_layer(); /* allow network layer ready events */
	ack_expected = 0; /* next ack expected inbound */
	next_frame_to_send = 0; /* next frame going out */
	frame_expected = 0; /* number of frame expected inbound */
	nbuffered = 0; /* initially no packets are buffered */
	while (true) {
	wait_for_event(&event); /* four possibilities: see event type above */
	switch(event) {
		case network_layer_ready: /* the network layer has a packet to send */
			/* Accept, save, and transmit a new frame. */
			from_network_layer(&buffer[next_frame_to_send]); /* fetch new packet */
			nbuffered = nbuffered + 1; /* expand the sender’s window */
			send_data(next_frame_to_send, frame_expected, buffer);/* transmit the frame */
			inc(next_frame_to_send); /* advance sender’s upper window edge */
			break;
		case frame_arrival: /* a data or control frame has arrived */
			from_physical_layer(&r); /* get incoming frame from physical layer */
			if (r.seq == frame_expected) {
				/* Frames are accepted only in order. */
				to_network_layer(&r.info); /* pass packet to network layer */
				inc(frame_expected); /* advance lower edge of receiver’s window */
			}
			/* Ack n implies n − 1, n − 2, etc. Check for this. */
			while (between(ack_expected, r.ack, next_frame_to_send)) {
				/* Handle piggybacked ack. */
				nbuffered = nbuffered − 1; /* one frame fewer buffered */
				stop_timer(ack_expected); /* frame arrived intact; stop timer */
				inc(ack_expected); /* contract sender’s window */
			}
			break;
		case cksum_err: break; /* just ignore bad frames */
		case timeout: /* trouble; retransmit all outstanding frames */
			next_frame_to_send = ack_expected; /* start retransmitting here */
			for (i = 1; i <= nbuffered; i++) {
				send_data(next_frame_to_send, frame_expected, buffer);/* resend frame */
				inc(next_frame_to_send); /* prepare to send the next one */
			}
	}
	if (nbuffered < MAX_SEQ)
		enable_network_layer();
	else
		disable_network_layer();
	}
}
  • 任何时候,可以发送的帧的最大个数不能等同于序号空间的大小。对回退n协议,可发送的帧最多为MAX_SEQ个,即使存在MAX_SEQ+1个不同的序号(分别为0、1、2、……、MAX_SEQ)。为了看清楚为什么会有这个限制,请考虑下面MAX_SEQ=7的场景:(1)发送方发送0~7号共8个帧(2)7号帧的捎带确认返回发送方(3)发送方发送另外8个帧,其序号仍然是0~7(4)现在7号帧的另一个捎带确认也返回了。问题出现了:属于第二批的8个帧全部到达成功了还是全部丢失(或者其中部分出错,把出错丢失的帧也当做丢失),这两种情况下,接收方都会对发送针对7号帧的确认,但发送方却无从辨认。由于这个原因,故有此限定。
  • 虽然协议5没有缓存出错后到来的帧,由于发送方必须保留发送出去的帧以便重传,它也没有完全摆脱缓存问题。当n号帧的确认到达,n-1号帧、n-2号帧等都会自动被确认,这种类型称为累计确认。当先前一些捎带确认的帧被丢失或者受损之后,这个特性非常重要。每当到达任何一个确认,数据链路层都要检查是否可以释放一些缓冲区。如果能释放出一些缓冲区,则原来被阻塞的网络层又可以激发更多的network_layer_ready事件了。
  • 对于这个协议,我们假设链路上总是有反向的数据流量可以捎带确认。因为协议5有多个未被确认的帧,逻辑上它需要多个计时器,每一帧的超时都是独立的。实现多个计时器只需要一个时钟(如图a),假设时钟每1毫秒滴答一次。初始时间为10:00:00.000;有三个超时事件,分别定在10:00:00.005、10:00:00.013、10:00:00.019;所有未发生的超时事件构成了一个链表,链表中的每个节点包含了离超时时钟还有多少时钟滴答、超时对应的那个帧以及一个指向下一个节点的指针。每当硬件时钟滴答一次,链表头上的滴答计数器就被减1,当滴答计数器变成0时,就引发一个超时事件,并将该节点从链表中移除,如图b所示。虽然这种实现方式要求在调用start_timer或stop_timer时扫描链表,但在每次滴答中断时并不要求更多的工作。在协议5中,start_timer和stop_timer这两个过程都带一个参数,表示针对哪一帧计时。(图a,队列中的计时器;图b,第一个计时器超时后的情形)
    在这里插入图片描述

1.3选择重传协议

  • 如果错误很少发生,则回退n协议可以工作得很好;但是,如果线路质量还差,那么重传的帧要浪费大量带宽。另一种处理错误的策略是选择重传协议,允许接收方接收并缓存坏帧或者丢失帧后面的所有帧。
  • 在这个协议中,发送方和接收方各自维持着一个窗口,该窗口分别包含可发送或已发送但未被确认的和可接收的序号。发送方的窗口大小从0开始,以后可以增大到某个预设的最大值。相反,接收方的窗口大小总是固定不变,其大小等于预先设定的最大值。接收方其窗口内的每个序号保留一个缓冲区。与每个缓冲区项相关联的还有一个标志位,用来指明缓冲区是满的还是空的。每到达一帧,接收方通过between函数检查它的序号,看是否落在窗口内。如果确实落在窗口内且之前没有接收过,则接收该帧并保存在缓冲区。以下代码是一个描述了该算法的协议。
/* Protocol 6 (Selective repeat) accepts frames out of order but passes packets to the
network layer in order. Associated with each outstanding frame is a timer. When the timer
expires, only that frame is retransmitted, not all the outstanding frames, as in protocol 5. */
#define MAX_SEQ 7 /* should be 2ˆn − 1 */
#define NR_BUFS ((MAX_SEQ + 1)/2)
typedef enum {frame_arrival, cksum_err, timeout, network_layer_ready, ack_timeout} event_type;
#include "protocol.h"
boolean no_nak = true; /* no nak has been sent yet */
seq_nr oldest_frame = MAX_SEQ + 1; /* initial value is only for the simulator */
static boolean between(seq_nr a, seq_nr b, seq_nr c)
{
	/* Same as between in protocol 5, but shorter and more obscure. */
	return ((a <= b) && (b < c)) || ((c < a) && (a <= b)) || ((b < c) && (c < a));
}
static void send_frame(frame_kind fk, seq_nr frame_nr, seq_nr frame_expected, packet buffer[ ])
{
	/* Construct and send a data, ack, or nak frame. */
	frame s; /* scratch variable */
	s.kind = fk; /* kind == data, ack, or nak */
	if (fk == data) s.info = buffer[frame_nr % NR BUFS];
	s.seq = frame_nr; /* only meaningful for data frames */
	s.ack = (frame_expected + MAX SEQ) % (MAX SEQ + 1);
	if (fk == nak) no_nak = false; /* one nak per frame, please */
	to_physical_layer(&s); /* transmit the frame */
	if (fk == data) start_timer(frame_nr % NR_BUFS);
	stop_ack_timer(); /* no need for separate ack frame */
}
void protocol6(void)
{
	seq_nr ack_expected; /* lower edge of sender’s window */
	seq_nr next_frame_to_send; /* upper edge of sender’s window + 1 */
	seq_nr frame_expected; /* lower edge of receiver’s window */
	seq_nr too_far; /* upper edge of receiver’s window + 1 */
	int i; /* index into buffer pool */
	frame r; /* scratch variable */
	packet out_buf[NR_BUFS]; /* buffers for the outbound stream */
	packet in_buf[NR_BUFS]; /* buffers for the inbound stream */
	boolean arrived[NR_BUFS]; /* inbound bit map */
	seq_nr nbuffered; /* how many output buffers currently used */
	event_type event;
	enable_network_layer(); /* initialize */
	ack_expected = 0; /* next ack expected on the inbound stream */
	next_frame_to_send = 0; /* number of next outgoing frame */
	frame_expected = 0;
	too_far = NR_BUFS;
	nbuffered = 0; /* initially no packets are buffered */
	for (i = 0; i < NR_BUFS; i++) arrived[i] = false;
	while (true) {
		wait_for_event(&event); /* five possibilities: see event type above */
		switch(event) {
		case network_layer_ready: /* accept, save, and transmit a new frame */
			nbuffered = nbuffered + 1; /* expand the window */
			from_network_layer(&out_buf[next_frame_to_send % NR_BUFS]); /* fetch new packet */
			send_frame(data, next_frame_to_send, frame_expected, out_buf);/* transmit the frame */
			inc(next_frame_to_send); /* advance upper window edge */
			break;
		case frame_arrival: /* a data or control frame has arrived */
			from_physical_layer(&r); /* fetch incoming frame from physical layer */
			if (r.kind == data) {
				/* An undamaged frame has arrived. */
				if ((r.seq != frame_expected) && no_nak)
					send_frame(nak, 0, frame_expected, out_buf); else start_ack_timer();
				if (between(frame_expected,r.seq,too_far) && (arrived[r.seq%NR_BUFS]==false)) {
					/* Frames may be accepted in any order. */
					arrived[r.seq % NR_BUFS] = true; /* mark buffer as full */
					in_buf[r.seq % NR_BUFS] = r.info; /* insert data into buffer */
					while (arrived[frame_expected % NR_BUFS]) {
						/* Pass frames and advance window. */
						to_network_layer(&in_buf[frame_expected % NR_BUFS]);
						no_nak = true;
						arrived[frame_expected % NR_BUFS] = false;
						inc(frame_expected); /* advance lower edge of receiver’s window */
						inc(too_far); /* advance upper edge of receiver’s window */
						start_ack_timer(); /* to see if a separate ack is needed */
					}
				}
			}
			if((r.kind==nak) && between(ack_expected,(r.ack+1)%(MAX_SEQ+1),next_frame_to_send))
				send_frame(data, (r.ack+1) % (MAX_SEQ + 1), frame_expected, out_buf);
			while (between(ack_expected, r.ack, next_frame_to_send)) {
				nbuffered = nbuffered − 1; /* handle piggybacked ack */
				stop_timer(ack_expected % NR_BUFS); /* frame arrived intact */
				inc(ack_expected); /* advance lower edge of sender’s window */
			}
			break;
		case cksum_err:
			if (no_nak) send_frame(nak, 0, frame_expected, out_buf); /* damaged frame */
			break;
		case timeout:
			send_frame(data, oldest_frame, frame_expected, out_buf); /* we timed out */
			break;
		case ack_timeout:
			send_frame(ack,0,frame_expected, out_buf); /* ack timer expired; send ack */
	}
	if (nbuffered < NR_BUFS) enable_network_layer(); else disable_network_layer();
	}
}
  • 非顺序接收引发了一些特殊问题。假设我们用3bit的序号,那么发送方允许发送7个帧,然后开始等待确认。刚开始时,发送方和接收方的窗口如图a所示,现在发送方发出0~6号帧,接收方的窗口接收任何序号落在0~6之间的帧。这7个帧全部到达了,接收方对它们确认,并且向前移动它的窗口,允许下一次接收7、0、1、2、3、4、5号帧,如图b所示,所有这7个帧都标记为空。
  • 如果闪电击中了电线杆,第一次发出的所有确认都被摧毁,这将导致发送方重传0号帧,不幸地是0号帧在接收方下一次期待的范围内,它将被当做新帧接收,接收方同样返回(捎带)6号确认帧(累计确认),因为0~6号帧已经都被接收,但作为下一批的第一个帧即7号帧没有到达,只到达了接收方认为的下一批的第二个帧即0号帧。发送方得到了确认后,它向前移动发送窗口,并立即发送7、0、1、2、3、4、5号帧。7号将被接收方,并且它的数据包直接传递给网络层。紧接着,接收方的数据链路层进行检查,看它是否有这批帧的第二个帧,它发现已经有了(即重传的0号帧),然后把其内嵌的数据包作为新的数据包传递给网络层。协议失败!
    在这里插入图片描述
  • 这个问题的本质在于:当接收方向前移动它的窗口后,新的有效序号范围与老的序号范围有重叠。因此,后续的一批帧可能是重复的帧(如果确认都丢失了),也可能是新的帧(如果所有的确认都接收到了),接收方无法区分这两种情况。解决这个问题的办法就是确保老窗口和新窗口的序号没有重叠。为了保证没有重叠,窗口的最大尺寸应该不超过序号空间的一半,如图c和图d一样。如果用3bit表示序号,则序号范围为0~7,任何时候,应该只有4个未被确认的帧。按照这种方法,如果接收方已经接受了0~3帧,并且向前移动了窗口,以便允许接收第4~7帧,那么它可以明确区分后续帧是重传帧(序号0~3)还是新帧(序号4~7)。一般来说,协议6的窗口尺寸为(MAX_SEQ+1)/2。
  • 接收方需要数量等同有窗口大小的缓冲区。在前面的例子中,只需要4个缓冲区就够了,编号为0~3。当i号帧到达时,它被放在(i mod 4)号缓冲区中。同样,计时器数量等同于缓冲区的数量,每个缓冲区都有一个相关联的计时器,当计时器超时时,缓冲区的内容就要被重传。因为协议的实现需要反方向上的数据帧来捎带确认消息。如果逆向流量很轻,确认会被延缓很长时间,这将导致问题:在极端情况下,如果在一个方向上有很大的流量,而另一个方向上根本没有流量,那么当发送方的窗口达到最大值后协议将被阻塞。为了解决该问题,当一个按正常次序发送的数据帧到达接收方之后,接收方通过start_ack_time启动一个辅助的计时器。如果在计时器之前,没有出现需要发送的反向流量,则发送一个单独的确认帧。由该辅助计时器超时导致的中断称为ack_timeout事件。辅助计时器的超时时间间隔应该明显短于数据帧关联的时间间隔,这是确保下列情况的必要条件:即一个帧被正确接收的帧应该尽早被确认,使得该帧的重传计时器不会因超时而重传该帧。
  • 协议6采用了比协议5更有效的策略来处理错误。当接收方有理由怀疑出现了错误时,它就给发送方返回一个否定确认(NAK)帧。这样的帧实际上是一个重传请求,在NAK中指定了要重传的帧。为了避免多次重传同一个丢失帧,接收方应该记录下对于某一帧是否已经发送过NAK。在协议6中,如果对于frame_expected还没有发送过NAK,则变量no_ack为true。如果NAK被损坏了,或者丢失了,发送方也会因超时而重传。如果一个NAK被发送出去后丢失了,而超时重传的帧又是一个损坏帧,则no_nak重置为true,并且辅助计时器将被启动,当该辅助计时器超时后,一个ACK帧将被发送出去,以便发送方同步到接收方的当前状态。
  • 如果发送方将计时器的时间设置为恰好略微大于正常情况下从发送一帧到接收到确认之间的时间间隔,此时NAK的作用就微乎其微了。如果刚好有反向流量捎带,一帧从发送到确认的时间就比较短;反之,发送方就要将时间间隔设置得比较小(但可能发送不必要的重传),或者设置得比较大(发生错误后长时间等待),但两者都会浪费带宽。一般来说,当确认间隔的标准偏差与间隔本身相比非常大的时候,计时器应该设置得长一点,然后NAK可以加快丢失帧或者损失帧的重传速度。
  • 与超时和NAK紧密相关的一个问题是由哪一帧引起的超时。在协议5中,引发超时的总是ack_expected指定的帧,因为它总是最早的那个帧。在协议6中,没有一种具体的办法来确定谁引发超时。假设已经发送了0~4号帧,这意味着未确认的帧是01234,按照时间先后顺序排列。现在想象一下这样的情景:0号帧超时,5号新帧发送,1号帧超时,2号帧超时,6号新帧发送。这时候,未确认的顺序是3405126,也是按照从最老的帧到最新的帧排列。如果所有确认帧都丢失,那么这7个未确认的帧将会依次超时。为了避免例子过于复杂,并没有描述计时器的管理过程;我们只是假设在超时变量oldest_frame设置好之后,它就能指出哪一帧超时。

猜你喜欢

转载自blog.csdn.net/ao__ao/article/details/85094401
今日推荐