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是异常情况;记号表示(序号,确认,包号),星号表示接受了一个包)