网络通信的目的是通过网络在应用进程间传输信息,任何数据丢失或损坏都将对通信双方产生重要的影响。因此,如何实现无差错的数据传输是一个非常重要的问题。
差错控制是指在网络通信过程中发现(检测)差错,并采取措施纠正,把差错限制在所允许的尽可能小的范围内的技术和方法。差错控制的目的是为了提高数据传输的可靠性,但是任何一种差错控制方法均不可能纠正所有可能出现的差错。差错控制主要有两种途径:
① 硬件途径。这种途径选用高可靠性的设备和传输媒体(如光纤)及相应的辅助措施(如屏蔽)来提高传输的可靠性。
② 软件途径。这种途径通过通信协议来实现差错控制。在通信协议中,通过差错检测、肯定确认、超时重传、否认重传、选择重传等措施来实现差错控制。
一般来说,根据差错发生的位置可以将网络通信中发生的差错分为三种类型:
① 通信链路差错。指有关通信链路上的故障、干扰造成的传输错误。
② 路由差错。指有关传输报文在路由过程中阻塞、丢失、死锁,以及报文顺序错而造成的传输差错。
③ 通信结点差错。指有关通信中某结点的资源限制、环境条件或文本不符、协议同步关系及操作错误、硬件故障等,可能影响通信链路的正确连接或正常通信的错误中断等。
根据差错的表现形式可以将差错分为:
① 失真(distortion):指被传送信息中的一个或多个比特发生了改变,或者被传送的信息中插入了一些新的信息(这种情况也称为插入)。导致数据失真的主要原因是:网络中物理干扰(如线路噪声),发送者和接收者之间的失步,入侵者的故意攻击,结点中的硬件故障和软件差错等。检测这种差错主要是通过各种校验方法来实现的。
② 丢失(deletion):指网络将被传输的信息丢弃。导致丢失的原因主要有:噪声脉冲对某个帧的破坏程度太大,以致接收方不知道这个帧已经被传输;发送者和接收者之间的失步;流量控制或拥塞控制措施不当时因资源不够而被中间结点或接收者丢弃;因接收者检测到信息被损坏而主动将其丢弃等。丢失可以用序号、计时器和确认共同检测,通过重传的方法来纠正错误。
③ 重复(duplication):指多次收到同样的信息。造成重复的主要原因是差错控制机制本身,为了实现信息的可靠传输,协议需要重传出错或丢失的数据。如果发送方错误地认为数据丢失而重传了它,就可能造成接收方收到重复的信息。还有一种情况就是路由选择机制引起的重复帧,如使用基于扩散的路由选择策略(如洪泛法)。可以用序号来检测这种错误,用丢弃重复的数据来纠正错误。
④ 失序(reordering):主要指数据到达接收方的顺序与发送方发送的顺序不一致。导致失序的主要原因有两个:其一,采用自适应的路由选择策略,分组在网络中传送时可能有多条路由而引起的后发先到;其二,重传丢失的数据也可能导致数据不按序到达。解决失序的方法主要有:把失序的数据先存储下来,使得以后能把它们存放在正确的位置上;丢弃失序的数据,然后按数据丢失来处理。
差错检测技术是指检查收到的数据是否正确的方法和技术。通常情况下,我们所说的差错检测是指检测收到的数据是否被损坏,而不包括对数据丢失、重复、失序等差错的检测。在这样的前提下,差错检测技术所采取的方法一般是各种检验和技术,如奇偶检验、循环冗余检验等,本节主要介绍这方面的技术。对数据丢失、重复、失序等差错的检测和纠正技术将在下一节介绍。
差错检测是差错控制的基础,其操作原理如图2-6所示。对于一组待传送的数据(1帧或1个分组),发送器为其添加一些额外的由差错检测码组成的比特。这些差错检测码是某个差错检测函数的输出结果,该函数的输入参数是被检测的传输比特。通常情况下,需要检测的传输比特是完整的1帧或1个分组(不包括差错检测码)。但在有些协议中,特别是在高层协议(如IP, TCP, UDP)中,只需要检测帧或分组的控制部分(首部),而不需检测帧或分组的数据部分。接收器执行同样的计算过程并比较这两个结果。当这两个结果不相同时,就表明有差错。
图2-6 差错检测原理
差错检测码通常放在被检测的传输比特的后面,这样做的好处在于减少处理时间。如果将检测码放在被检测的数据的前面,则需要处理两遍数据,第一遍是为了计算检测码,第二遍是为了发送。而如果将检测码放在被检测的数据的后面,一旦发送完最后1位数据,检测码也已计算出,发送器就可以立即把检测码附加在输出流的后面发出,这样做可以将处理时间减半。
一般来说,一种差错检测技术很难检测到所有可能发生的差错。我们将尽管使用了差错检测机制也无法检测出来的差错的概率称为剩余差错率。
奇偶检验是指在数据的尾部附加上奇偶检验位,使得码字中比特“1”的个数保持为奇数(奇检验)或偶数(偶检验)。它是一种最简单的差错检测机制,被广泛用于诸如计算机的异步串行口通信中。在通信中使用时又可以分为垂直冗余检验(VRC,Vertical Redundancy Checking)、纵向冗余检验(LRC,Longitudinal Redundancy Checking)和纵向垂直冗余检验。
垂直冗余检验是将发送的信息分为长度为p位的若干段,如q段,每段后面按“1”的个数为奇数或偶数的规律加上一位奇偶位,其有p × q个信息位,每段由p位构成,共q列(段)。可以用硬件或软件方法来实现连续半加运算,且可以边发送边产生检验位,并插入发送。在接收时边接收边检验并除去检验位。
通常p值等于一个字符的长度,因此有时也将垂直冗余检验称为字符奇偶检验。这种奇偶检验方法能检测出每列中的所有奇数个位的错,但检测不出偶数个位的错。对于突发错误而言,奇数位错与偶数位错的概率差不多是相等的,因而对差错的漏检率接近50%。
为了降低对突发错误的漏检率,人们又提出了纵向冗余检验。它是对各个信息段的相应位水平进行编码,对应每一行产生一个奇偶检验位。这种附加的水平基础上检验字符的技术,使用了和垂直冗余检验同样的奇偶性。纵向冗余检验不但可以检测出各段同一位上的奇数位错,而且可以检测出突发长度小于等于p的所有突发错误,因为可以安排发送顺序使得在可见突发长度小于等于p的突发错误必然分布在不同行中,每行一位,所以可以检测出来。它的漏检率要比垂直冗余检验低,但是实现时不能在发送过程中边产生奇偶检验位,边插入发送,而必须要等到要发送的完整信息块到齐后,才能产生检验位,因而一定要使用记忆寄存器。因此,这种方法的编码和检测实现都要复杂一些。
同时进行垂直冗余检验和纵向冗余检验就构成了纵向垂直冗余检验。它能检测出所有3位或3位以下的错误(因为此时至少在某一行或某一列上为一位错)、奇数位错、突发长度小于等于( p + 1 )的突发错,以及很大一部分偶数位错。
纵向垂直冗余检验还可以纠正部分差错,例如,仅在某一行和某一列中有奇数位错时,则可以确定错误的位置就在该行和该列的交叉处,从而得以纠正。此外,当某一行出现偶数个错时,虽然纵向冗余检验不能发现,但在其垂直冗余检验中还是可以被检测出来。只有当有错的各行和各列中出错位数均为偶数时,才不可能被发现。图2-7示出了一个纵向垂直冗余检验的例子。
图2-7 纵向垂直冗余检验举例
使用奇偶检验并不是十分安全,因为噪声脉冲的长度经常足以破坏一个以上的比特,特别是在数据率较高的情况下。因此,低速率的串行口通信协议,如RS232接口,常采用奇偶检验方法来检测错误。一般情况下,垂直冗余检验主要用于异步传输,且一列对应一个字符。而纵向冗余检验则常用于同步传输,通常是把一串字符作为一个块传送,字符之间没有时间间隔,产生一个附加的字符跟在块的后面。因此人们还把这种纵向冗余检验编码称为块检验码(BCC,Block Checking Code),把附加的字符称为块检验符。
循环冗余检验(CRC,Cyclic Redundancy Checking)是一种最常见的,也是最有效的差错检测编码,它的漏检率比奇偶检验要低得多,也容易实现。下面我们用一个具体的例子来说明循环冗余检验的原理。
假设待传送的数据M = 1010001101(共k比特)。我们在M的后面再添加供差错检测用的n 比特冗余码一起发送。在所要发送的数据后面增加一些冗余码,虽然增大了数据传输的开销,但却可以进行差错检测。在传输可能出现差错时,付出这种代价还是值得的。
这n bit冗余码是这样得出的。用二进制的模2运算(用模2运算进行加法时不进位,减法和加法是一样的。例如,1111 + 1010 = 0101)。进行2 n 乘M的运算,这相当于在M后面添加n个0。得到的(k + n)比特的数除以事先选定好的数P(P的长度为(n + 1)比特),得出的商是Q而余数是R,余数R比除数P至少要小1个比特。至于P是怎样选定的,下面还要介绍。在图2-8所示的例子中,n = 5,P = 110101,模2运算的结果是:商Q = 1101010110,而余数R = 01110。现在将得到的余数R作为冗余码添加在数据M的后面发送出去,即发送的数据是101000110101110,或2 n M + R。
为检测差错而在数据后面添加上的冗余码常称为帧检验序列(FCS,Frame CheckSequence)。帧检验序列是要保证收到的数据和发送的数据完全相同。
如果数据在传输过程中不产生误码,则接收端收到的应当是2 n M + R。将这个数除以P(模2运算)后,得出的余数显然应当是0(读者可以自己进行类似图2-8的运算。被除数现在是101000110101110,而除数是P = 110101,看余数是否为0)。若数据在传输过程中出现误码,则在接收端进行以上运算后,一般就不会得出余数为0的结果。
图2-8 循环冗余检验原理
一种较方便的方法是用多项式来表示循环冗余检验过程,即使用多项式相应的系数来表示上述二进制数字中的1和0。例如,可以用多项式P(X) = X 5 + X 4 + X 2 + 1来表示上面的除数P(称为生成多项式)。因此,在接收端进行的运算可写为
只要得出的余数R ≠ 0,就表示检测到了差错(注意:这种检测方法并不能确定究竟是哪一个或哪几个比特出现了差错)。
那么能不能说,只要得出的余数R = 0,就一定没有出现差错呢?不可以!因为在某种比特差错的组合下,也可能碰巧使得余数R正好为0。但只要经过严格的挑选,并使用位数较多的除数P,那么出现检测不到的差错的概率就可以是个很小的数值。现在广泛使用的P(X)有以下几种:
CRC检验的漏检率是比较低的。在上述多项式中,CRC-CCITT检验可以检测出所有的双比特差错,长度为17比特的数据中的99.997%的突发错误,长度大于17比特的数据中的99.998%的突发错误。数据链路层协议(HDLC)采用的就是CRC-CCITT检验。IBM的Bisync协议中采用的是CRC-16检验,以太网帧和光纤分布式数据接口标准(FDDI)中采用的则是CRC-32检验。
CRC检验的编码需要花费不少的时间,因此计算检验和可能会降低协议的性能。为了提高协议的性能,通常采用硬件方法(移位寄存器)或查检验和表(预先计算出检验和放在表中,使用时查表即可)的软件方法来实现。
下面的C程序crc_init.c是AT&T Bell实验室的Don Mitchell所写的一个计算CRC检验和,并生成检验和表的程序。
程序crc_init.c的源代码如下:
程序crc_init.c的功能是创建检验和表(crc_table[])。在UNIX系统中编译crc_init.c,生成可执行程序crc_init。运行crc_init即可产生基于某一多项式的检验和表。例如,命令:
“$ crc_init 05401 > crc_12.h”创建基于CRC-12的检验和表,并保存在文件crc_12.h中。
“$ crc_init 0120001 > crc_16.h”创建基于CRC-16的检验和表,并保存在文件crc_16.h中。
“$ crc_init 0102010 > crc_ccitt.h”创建基于CRC-CCITT的检验和表,并保存在文件crc_ccitt.h中。
然后,可以使用函数cksum(s, n)查表生成检验和。函数cksum(s,n)的输入参数s为被检验的数据,n为数据长度(单位为字节)。函数返回值为被检验的数据的检验和。定义cksum(s,n)的源程序中必须包括crc_init创建的包含检验和表的头文件。cksum(s, n)的源代码如下:
与CRC检验比较类似的一种检验方法是UNIX操作系统中uucp应用程序中计算检验和的方法,该方法的C函数cksum(s, n)实现的源代码如下:
上述方法比较简单,有点类似于散列方法,其漏检率比CRC要高一些,而且计算同样的检验和,它所花的时间比查表计算CRC检验和的方法要多一些。
尽管可以通过前面介绍的查表的方法或特殊硬件的方法计算检验和来缩短处理时间,但对于那些允许一定程度的漏检率存在的应用而言,可以采用比CRC检验简单且能够发现比较严重的传输错误的方法来进行差错检测。John Fletcher 在1982提出了这样一种差错检测方法,称为算术检验和。该算法只需用到加法和取模操作,且算法特别简单。下面的代码取自于ISO的类4运输协议(TP4),它是算术检验和的一种实现:
一般情况下,使用差错检测技术检测出传输错误后,通常使用重传(请求重传和超时重传)的方法(在下一节介绍)来纠正。但是,在以下一些场合下,不适合或不能使用重传的方法:
(1)很长的传输时延。例如,空间探测器与地面控制中心之间的通信,可能没有足够的时间来重传。
(2)没有反向信道。例如,在单向广播系统中,根本不能发送重传请求。
(3)高的比特差错率。在这种情况下,重传帧及请求重传帧出错的概率都非常高,使得重传难以实现。
(4)在某些实时性应用中,没有时间来实现重传或重传的信息因时间过长而变得无意义。
因此,需要采用前向纠错(FEC,Forward Error Correction)的方法,即在每个要发送的数据块中附加上足够的冗余信息(纠错码),使接收方能够推导出发送方实际发送的应该是什么样的比特串。前面介绍的纵向垂直冗余检验就具有纠错功能。
前向纠错技术需要进行前向纠错编码。目前已出现很多种前向纠错编码,主要分为两类:卷积码(Convolution Codes)和分组码(Block Codes)。两者的不同之处在于:卷积码的码字不仅与被编码的数据块有关,还依赖于先前已编码的数据的编码;而分组码则是接收可变长度的数据块,对每个数据块独立地进行编码。比较著名的前向纠错码是海明码(Hamming Codes)和BCH码(Bose、Chaudhuri及Hocquengham)。下面以海明码为例来说明前向纠错技术的原理。
海明码的编码是将码字内的位从最左边开始按顺序依次编号,第1位是1号,第2位是2号,……,第n位是n号。编号为2的幂的位(1号位,2号位,4号位,8号位等)是检验位,其余的位填入m位数据。用来校正单比特误码的检验位数目r的下界是:2 r ≥ n + 1。每个检验位的取值应使得包括自己在内的一些位的集合服从规定的奇偶性(例如,偶性要求这些位的集合中1的个数是偶数)。为了知道编号为k的数据位对哪些检测位有影响,将编号k改写成2的幂的和,例如,11 = 1 + 2 + 8,29 = 1 + 4 + 8 + 16。每个位只由扩展式中所示编号的位检测。该例中编号为11的位只由编号为1、2和8的检测位检测。
为了说明如何为每个检验位取值,这里以一个7位ASCII字符使用海明码编码形成11位码字为例来说明。这里,m = 7, r = 4, n = 11,显然2 r > 11 + 1 =12, 而且编号1 = 1, 2 = 2,3 = 1 + 2, 4 = 4, 5 = 1 + 4, 6 = 2 + 4, 7 = 1 + 2 + 4, 8 = 8, 9 = 1 + 8, 10 = 2 + 8, 11 = 1 + 2 + 8, 于是有:
(1) → (1) + (3) + (5) + (7) + (9) + (11),(2) →(2) + (3) + (6) + (7) + (10) + (11)
(4)→ (4) + (5) + (6) + (7),(8) → (8) + (9) + (10) + (11)
注意,在每个检验位的形成表达式中,除自身的编号外,其余都是信息位的编号,因此只要信息位是确定的,检验位也可以唯一地确定。表2-3列出了10个7位ASCII字符使用海明码编码形成的10位码字,其中数据位在第3, 5, 6, 7, 9, 10和11位,编号为1, 2, 4, 8的位是检验位。
当一个码字到达时,接收方将计数器清零。然后接收方检测每个检验位D(这里的D是检验位的编号),看是否具有正确的奇偶性。如果第D位奇偶性不对,则计数值加D;若所有检验位被检查过后,计数器值仍为零,这个码字就作为有效码字接收。假如计数器值不为零,则该值就是出错位的编号。例如,若检测位1、2和8错误,则第11位就变反,因为它是唯一被第1、2和8位检测的位。
表2-3 使用海明码纠正突发差错示例
可以看出,海明码的信息余量很大,因而编码效率低,比7位ASCII码字要增加4个冗余位才行。这就大大增加了数据通信的开销。因此,海明码的使用不如重传方式的纠错普遍。
前向纠错技术必须使用纠错码,它不需要利用反向信道来传递请求重传的信息,也不需要分配实施重传的数据缓存。虽然FEC有上述优点,但由于纠错码一般都要使用比检错码更多的冗余位,编码效率低,而且纠错算法也要比检错算法复杂得多,因而除非在单向传输或实时性要求特别高(FEC由于不需要重传,实时性较好)等场合外,数据通信中使用更多的还是差错检测和重传相结合的差错控制方式。在有些协议中,将检错码和纠错码结合起来一起使用,以增强差错控制的能力,如ATM AAL1协议中的差错检测机制(见表2-4)。
表2-4 不同协议中的检验和的保护对象
除了前面介绍的几种检测技术以外,还有很多个性化的检测技术。例如,卫星通信地球站内的射频设备的计算机控制接口一般都是RS-232接口,且大多数都采用奇偶检验方法,但有的设备却采用了特殊的检验方法,如SDS International 公司的上行功率控制器的计算机控制接口的通信协议中的检验和的计算方法如下:
上式的含义是:将报文的所有字符的ASCII码值减去32后相加,将所得结果进行模95后,所得值加上32即为检验和字节。
有些协议是对整个数据单元(帧、分组、报文,或统称为PDU)进行检验和保护的,而另一些协议则只需要对数据单元的首部(控制部分)或首部中的某个字段进行检验和保护。选择被保护对象的主要依据是根据协议提供的功能,下层协议提供的服务的特点,以及性能上的要求等。表2-4中列出了部分协议的检验和的保护对象。
在IP和ATM协议中,检验和都仅覆盖首部而不包括数据。这样做的主要原因是在首部中的错误比在数据中的错误更严重。例如,一个错误的地址可能导致分组被投递到错误的主机。许多主机并不检查投递给它们的分组是否确实是要投递给它们的。它们假定网络从来不会把本来是要前往另一主机的分组投递给它们。有的时候数据不参与检验和的计算,因为这样做代价大,上层协议通常也做这种检验工作,从而引起重复和多余。
ATM的AAL1协议支持的业务对定时的要求非常高。信元丢失和时标(timing stamp)损坏对定时恢复有决定性的影响。通过检查信元首部中的序号计数器字段,不仅可以查出信元丢失的差错,还可以标志是哪一个信元丢失。因此,AAL1协议对该字段提供了保护。此外,AAL1协议的差错控制机制还可以纠正序号字段和时标字段中的1比特错误,对负载字段还可以提供可选的前向纠错的差错控制机制。
ATM的AAL5协议主要是为数据传输而设计的,因此差错保护是AAL5的一项很重要功能。AAL5不是对每个信元都进行CRC保护,而是提供了对CS-PDU的CRC保护,可以检测出比特错误、信元丢失和信元误递(因为被损坏的信元被路由到错误的目的地)等差错。有关ATM协议的详细情况见参考文献[10]。
检验和的计算函数一般是以被保护对象作为输入参数的。但也有一些例外,如表2-4中所示的TCP和UDP协议的检验和的计算函数的输入参数为一个12字节的伪首部(IP分组的首部)加上被保护的分组,这样做的原因是为了保护IP分组首部以免它可能被交付到错误的主机。关于TCP和UDP分组中的检验和的详细情况,见参考文献[5]。
上一节已详细介绍了用于检测信息被“损坏”的各种检测技术。本节主要介绍丢失、重复、失序等错误的检测技术,以及各种差错的恢复技术。这些技术涉及的概念主要有:确认、计时器、重传和序号。
确认(ack)是接收者显式地通知发送者所发送的特定数据的接收情况。被确认的对象通常是数据PDU,但在有些面向字节流的传输协议(如Internet协议族中的TCP协议)中被确认的对象是字节。确认主要包括三种情况:已正确到达,还没有收到,收到但有错(相当于没有收到)。通常将确认分为三种类型:肯定确认、否定确认、选择确认。肯定确认指示数据已正确收到;否定确认指示数据丢失(没收到或收到但有错误);选择确认既指示已正确接收的数据PDU,又指示哪些数据PDU还没有正确收到。
确认通常有两种存在方式,一种方式是独立确认,另一种是应答携带(piggybacking)。独立确认是指用一个确认PDU来携带确认信息,而应答携带则是指将确认信息放在数据PDU中发送。应答携带技术可以提高协议的效率,但要求接收方有数据发送时才能发送确认。而独立确认则随时可以发送。通常在一种协议中,这两种确认形式均存在。
在确认信息中,被确认的数据PDU用它们的序号来标志。否定和肯定确认由一个序号构成。如果否定确认或肯定确认的语义是表示所给定的序号之前的所有序号的数据PDU都已被成功地接收了,则该确认又称为累计确认。选择确认信息中包含多个序号,其格式可以有以下几种:
① 表。表中含有一组序号,或者代表丢失的数据PDU,或者代表正确接收的数据PDU。
② 范围。范围用序号区间来表示,在区间内的序号或者代表丢失的数据PDU,或者代表正确接收的数据PDU。
③ 位图。用一个比特组来表示确认,其中每一个比特位代表一个序号,它被置位表示它所代表的数据PDU已正确收到。此外,这种方法还需要一个序号,指明该位图的偏移量。
肯定确认、否定确认和选择确认的语义如图2-9所示。图(a)表示的是肯定确认的语义,其中(1)表示序号n对应的帧已正确收到,(2)表示直到n的所有序号对应的帧已正确收到(累计确认)。图(b)表示的是否定确认的语义,其中(3)表示序号n对应的帧已丢失,(4)表示直到n-1的所有序号对应的帧已正确收到,但第n帧还没有收到(累计确认),(5)表示直到n-1的所有序号对应的帧已正确收到,但从n到最后序号对应的所有帧还没有收到(累计确认)。图(c)表示的是选择确认的语义,(6)表示除了所列出的序号所对应的帧以外,直到n的所有序号对应的帧已正确收到。
图2-9 确认的语义
产生一个确认PDU的触发机制为以下几种主要形式之一,或是它们的某种组合。
(1)在接收了n个数据PDU之后,接收者产生一个确认PDU。
(2)发送者产生一个信号,请求产生一个确认PDU;信号可以随同一个数据PDU一起传递,也可以是一个专用的控制PDU,如数据链路层协议HDLC中的P/F位。
(3)接收者直到数据PDU不连续到达时,才产生一个确认PDU。
(4)接收者周期性地产生确认PDU,与数据PDU的到来无关。
(5)接收者重复收到同一个数据PDU,接收者发送确认PDU,防止确认丢失。
(6)接收者在发送的数据中携带确认信息。
通常在一个协议中,多种确认形式并存。选择确认常用于高速传输协议中,如ATM的信令传输协议。
如果携带确认信息的确认PDU或数据PDU丢失,则发送方无法知道发送的PDU的接收情况。对于实现可靠的数据传输协议而言,如果没有收到确认则不能释放已发送的数据PDU所占用的缓存,也可能因为流量控制机制的作用而不能发送新的数据PDU。为了解决这一问题,需要使用重传计时器来检测确认PDU或重传请求信号的丢失。
计时器的值依赖于PDU的往返时间,即一个PDU从发送者传输到接收者,然后再传回所需要的时间。往返时间的确定是一个非常复杂的事情,与网络负载和路由选择策略有很大的关系,通常情况下是一个动态变化的量。动态地估计往返时间和重传计时器的定时值的算法比较多,如RFC793中定义的TCP算法、Mills算法(Mills, 1983)、Edge算法(Edge,1984)、Karn和Patridge的算法(Karn and Patridge, 1987),以及Jacobson/Karel的算法(Jacobson, 1988)等。
如果超时计时器的超时值设置不当,可能导致连续大量的数据重传,严重情况下将加剧网络的拥塞程度,出现更多的数据丢失。如果超时值设置得太长,出现数据丢失而得不到即时纠正,也会降低协议的性能。
为了差错控制的目的,一个协议中往往存在多个计时器,表2-5中列出了OSI的类4运输协议中与差错控制有关的计时器,其中TPDU为运输协议数据单元,CR、CC、DT、ED、DR为TPDU的类型。
表2-5 OSI类4运输协议中的计时器
下面再来介绍TCP协议中的计时器。为了实现差错控制功能,TCP协议设置了4个计时器:重传计时器,坚持计时器,保活计时器和时间等待计时器。为了控制丢失或丢弃的报文段,TCP使用处理重传时间(即对报文段的确认的等待时间)的重传计时器。当TCP发送报文段时,它就创建该特定报文段的重传计时器,收到确认后就撤销此计时器,计时器超时时重传此报文段。TCP为每一个连接使用一个坚持计时器。当发送端的TCP收到一个窗口大小为零(让发送端停止发送)的确认时,就启动坚持计时器。保活计时器用在某些实现中,用来防止在两个TCP之间的连接处于长时间空闲。如果在保活计时器的超时间隔内没有收到客户的信息,它就发送探测报文段以确定客户是否已出现了故障。时间等待计时器是在连接终止期间使用的。当TCP关闭一个连接时,它并不认为这个连接马上就真正地关闭了,在时间等待期间中,连接还处于一种中间过渡状态。
重传是指发送者重传由确认所指出的数据PDU或重传计时器超时时重传未收到确认的数据PDU。主要有两种基于滑动窗口(参考2.5节)的重传方法。
(1)回退n帧(Go-Back-N)。接收方直接丢弃所有不按序到达的数据PDU。并且对于丢弃的数据PDU,不发送确认。如果重传计时器超时之前,发送方的发送窗口已满,则发送方停止发送。最终,发送方从一个否定的确认或超时时所指序号的数据PDU开始,重传所有后续数据PDU。数据链路层的连续ARQ协议使用的就是这种重传方式。图2-10示出了一个回退n帧的重传机制。
图2-10 回退n帧的重传机制
(2)选择重传(Selective Repeat)。发送方只重传否定确认、选择确认和超时中指出的那些数据PDU。数据链路层的选择重传ARQ协议使用的就是这种重传方式。图2-11示出了一个选择重传。
图2-11 选择重传
实际协议中的重传机制比较复杂,主要是超时计时器的超时间隔很难确定。下面以Internet中的TCP协议为例来说明协议中的重传机制 [19] 。
重传机制是TCP中最重要和最复杂的问题之一。TCP每发送一个报文段,就设置一次计时器。只要计时器设置的重传时间到而还没有收到确认,就要重传这一报文段。大家知道,TCP的下层往往是一个互联网环境。发送的报文段可能只经过一个高速率的局域网,但也可能经过多个低速率的广域网,并且数据报所选择的路由还可能会发生变化。图2-12示出了数据链路层和运输层的往返时延概率分布的对比。往返时延就是从数据发出到收到对方的确认所经历的时间。对于数据链路层,其往返时延的方差很小,因此将超时时间设置为图2-12中的T1即可。但对于运输层来说,其往返时延的方差很大。若将超时时间设置为T2,则很多报文段的重传时间太早了,会给网络增加许多不应有的负荷。但若将超时时间选为T3,则显然会使网络的传输效率降低很多。
图2-12 数据链路层和运输层往返时延的概率分布
那么,运输层的超时计时器的重传时间究竟应设置为多大呢?
TCP采用了一种自适应算法。这种算法记录每个报文段发出的时间,以及收到相应的确认报文段的时间,这两个时间之差就是报文段的往返时延。将各个报文段的往返时延样本加权平均,就得出报文段的平均往返时延T。每测量到一个新的往返时延样本,就按下式重新计算一次平均往返时延:
平均往返时延T = α ×(旧的往返时延T′) + (1 − α)× 新的往返时延样本 (2-1)式中,
若α很接近于1,表示新算出的往返时延T和原来的值相比变化不大,即新的往返时延样本的影响不大(T值更新较慢)。若α接近于零,则表示加权计算的往返时延T受新的往返时延样本的影响较大(T值更新较快)。典型的α = 7/8。
显然,计时器设置的重传时间应略大于上面得出的平均往返时延,即
这里 β 是一个大于1的系数。实际上,系数 β 是很难确定的。若取 β 很接近于1,发送端可以很及时地重传丢失的报文段,因此效率得到提高。若报文段并未丢失而仅仅增加了一点时延,那么过早地重传未收到确认的报文段,反而会加重网络的负担。因此TCP原先的标准推荐将 β 值取为2。但现在已有了更好的办法。
上面所说的对往返时间的测量,实现起来相当复杂。请看下面的例子。
发送出一个报文段。重传时间到了,还没有收到确认。于是重传此报文段。后来收到了确认报文段。现在的问题是:如何判定此确认报文段是对原来的报文段的确认,还是对重传的报文段的确认?由于重传的报文段和原来的报文段完全一样,因此源站在收到确认后,就无法做出正确的判断。
若收到的确认是对重传报文段的确认,但却被源站当成是对原来的报文段的确认,那么这样计算出的往返时延样本和重传时间就会偏长。如果后面再发送的报文段又是经过重传后才收到的确认报文段,那么按此方法得出的重传时间就越来越长。
同样,若收到的确认是对原来的报文段的确认,但被当成对重传报文段的确认,则由此计算出的往返时延样本和重传时间都会偏短。这就必然导致报文段的重传。这样就有可能导致重传时间越来越短。
根据以上所述,Karn提出了一个算法:在计算平均往返时延时,只要报文段重传了,就不采用其往返时延样本。这样得出的平均往返时延和重传时间当然就比较准确。
但是,这又引起了新的问题。设想出现这样的情况:报文段的时延突然增大了很多。因此在原来得出的重传时间内,不会收到确认报文段。于是就重传报文段。但根据Karn算法,不考虑重传的报文段的往返时延样本。这样,重传时间就无法更新。
因此要对Karn算法进行修正。方法是:报文段每重传一次,就将重传时间增大一些:
系数 γ 的典型值是 2。当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延和重传时间的数值。实践证明,这种策略较为合理。
为了检测数据PDU的重复、失序和丢失,需要对数据PDU进行无二义性的编号,该编号称为数据PDU的序号。通常编号是按照请求服务者传递SDU的次序进行的。这些SDU被放入PDU中进行传输。序号有三种不同的产生方式:
① SDU序号。对每个SDU都编上序号,从SDU序号得到PDU的序号。如果该SDU被携带在多个数据PDU中时,还应附加一个SDU数据块号。
② PDU序号。对PDU连续编号,不管它们携带的SDU数据量。
③ 字节序号。一个SDU的每个字节都编号,PDU的序号来自于它所携带的SDU的第一个字节的序号和最后一个字节的序号。TCP协议中使用的就是这种编号方式。
如何防止序号重复是一个非常重要的问题。下面以面向连接的运输层协议为例来研究这个问题。在一个不太可靠的网络或几个串接的网络(其中至少有一个是不可靠的)上传送运输数据时,最基本的一个问题就是:尽管有些分组在相当长的一段时间内无法交付到收端,同时在这段时间内连接释放和又建立可能发生多次,我们仍然要求协议能正确地工作。数据单元必须编上序号,这样才能检测出重复的数据单元及不按顺序到达的数据单元。与超时控制相配合的肯定确认,使得丢失的和收到但有差错的数据单元能够正确地重传。现在来考虑这样一种情况,即在一个运输连接释放时,网络中还存在有未交付的数据单元,但是当新的运输连接建立后,这个旧的数据单元又交付给收端了。在一定的条件下,这就会导致收端无法区分收到的数据单元是新的还是旧的数据单元,从而产生差错。
解决这个问题可以有多种方法。一种可能的方法是使用一个非常大的序号空间。这个序号空间大到使得在数据单元从这一端到另一端的最大可能的迟延时间L内,所有从源点发送出去的新的数据单元都具有不同的序号。在开始一个新的连接时,可以使数据单元的起始序号等于上次连接中最后使用过的序号递增,这样就可以将新旧数据单元区分开来,因而不会产生重复。但问题是,一方面可能需要一个特别大的序号空间,另一方面,连接的每一端必须记住每一次的连接要从哪个序号开始。更严重的问题是,当有一个用户系统出了故障但不久又恢复正常工作时,该系统又如何记得起来应该使用哪个序号呢?
另一种解决问题的方法是,每次连接开始的数据单元都使用同样的起始序号,但必须使新的连接推迟开始,如至少在上次的连接释放后推迟时间L。或者赋给每个连接一个标号,称为连接序号。对于一个给定连接中的每一个数据单元都附上连接序号。当新建一个连接时,就换上一个新的序号。这样,不同连接中的数据单元就不致弄混。Internet中的插口地址(socket address)采用的就是这种方法。为了防止重复的数据单元引起的混乱,一个新的连接不能使用原来已用过的序号,除非在建立这个连接时,上次连接中所发送的分组均已超过数据单元最大迟延这一门限。在ISO的类4协议中,将数据单元最大迟延称为参照时间(reference time)。在一些文献中,则称为最长分组寿命。如何估计参照时间L是一件很不容易的事。时钟驱动法(clock-driven scheme)很好地解决了序号重复问题,有兴趣的读者可以参考文献[12]。
序号空间的大小与信道特点、确认方法、流量控制方法和PDU中的数据字段长度有关。例如,如果信道的质量比较好,则要求序号空间比较大,从而可以连续发送多个数据单元,提高协议效率。采用周期性确认,序号空间就不一定很大。在停止等待协议中,只需要0和1两个序号即可。一般来说,序号空间的大小与数据字段的长度成反比。
序号是确认和重传的基础,此外序号还可用于流量控制。
下面我们从分层的网络体系结构的观点来分析各层差错控制机制的特点。在网络体系结构中,从层的功能分工上看,物理层、数据链路层和网络层属于网络功能,运输层、应用层属于用户功能。从通信和信息处理的角度看,物理层、数据链路层、网络层和运输层属于面向通信部分,因而网络中的绝大部分差错控制功能要在这几层中实现。
物理层和数据链路层主要处理通信线路引起的传输错误,这类错误大多是随机偶然性错误,一般通过检测、重传的方式来纠正。考虑到物理层一般主要依靠硬件实现,重传纠错比较困难,所以原则上把差错恢复的任务交给数据链路层来解决。因而,物理层一般不保证向数据链路层提供无差错的数据传输服务。在数据链路层通常以帧为单位构造CRC检验和,接收方发现CRC检验有错时,发送确认信息或丢弃出错帧,发方超时重传出错的帧。同时,通过帧序号来发现因重传导致的帧重复。在有些情况下,物理层也进行奇偶检验,以供维护、诊断和分析用,如现在有些MODEM也可进行差错重传控制。通过以上的差错控制后,数据链路层能向上层(一般是网络层)提供无差错的数据传输服务(按序、无丢失、不重复)。
网络层的主要任务是提供路由选择和网络互连功能。对于路由转发过程中由于拥塞、缓存溢出、死锁、老化及非法格式等引起的报文丢失、失序等差错,网络层一般只做差错检测而不做纠错处理,只是把不能接收或过期非法的分组简单丢弃或向发送方报告。纠错则放在运输层处理。但是,如果网络层提供虚电路服务,由于虚电路服务能保证报文无差错、不丢失、不重复且按序地进行交付,因此网络层就需要实现纠错功能。
运输层向应用进程提供端到端的无差错的数据传输服务。通信子网(只包括网络层、数据链路层和物理层)所提供的服务越多,运输层协议就可以做得更简单。例如,网络层提供虚电路服务时,运输层协议就很简单。但是,即使网络层提供的是虚电路服务,某些用户仍可能怀疑下面的网络是否100%可靠,因而在网络层上面加上用户自己的端到端差错控制和流量控制。例如,X.25网络虽然提供虚电路服务,但当网络中的虚电路进行重建(Reset)时,主机就无法获得正在网内的分组的状态,恢复工作必须由高层即运输层来进行。再比如,假定所有的路由器和主机都工作正常,所有软件的运行也都没有错误,网络还是有可能(尽管可能性很小)将分组投递到错误的目的地的。这是因为大的突发噪声可能破坏分组。使用k位的CRC检验和,差错仍然有2-k的概率被漏检。如果分组的目的地址字段或虚电路号被改变,且没有被检测出来,则分组将被投递到错误的目的地,并可能被接收为正确的分组。换句话说,偶然的突发噪声可能把送往一个目的地的完全合法的分组改变成送往另一个目的地址的完全合法的分组。
运输层通常需要解决的差错包括丢失、重复、失序,采取的差错控制措施包括序号、确认、超时、重传等。为了进一步说明网络层和运输层的差错控制功能的分工,我们来看看OSI关于运输协议的分类。
为了能够在各种不同网络上进行不同类型的数据的传送,OSI定义了5类运输协议,即类0、1、2、3和4。这5类协议都是面向连接的。也就是说,用户要进行通信,必须先建立运输连接。当然,这必然要用到网络层提供的服务。OSI将网络服务质量分为3种类型:
(1)A型。A型网络连接具有可接受的差错率(即残留差错率或漏检差错率)和可接受的故障通知率。
(2)B型。B型网络连接具有可接受的差错率和不可接受的故障通知率。
(3)C型。C型网络连接具有不可接受的差错率(对运输服务用户来说)。
A型网络服务是一个可靠的网络服务。分组在网络中传送时不会丢失也不会失序。这样运输层就不需要故障恢复的服务和重新排序的服务等。在网络层采用虚电路服务的网络即属A型。
对于B型网络连接,运输协议必须提供差错恢复的功能。
C型网络服务的质量最差。对于这类网络,运输协议必须能够检测出网络的差错,同时要有差错恢复能力。对于失序、重复及错误投递的数据分组,也应能检测出来并进行改正。某些局域网、一些具有移动节点的城域网以及具有衰落信道的分组无线电网都属于C型网络。
表2-6 列出了OSI运输协议的类别与网络服务质量的关系。从表中可以看出,运输协议的差错控制机制与底层网络服务质量的关系。
表2-6 OSI运输协议的类别
通过数据链路层、网络层及运输层的上述检测纠错措施后,一般来说,运输层逻辑链路提供给应用层用户的服务将是一条无差错的可靠的数据传输通道。因此,应用层一般可不考虑传输上的错误。但应用层仍然有它自己的差错控制问题,这主要是协议类型不一致、文本或文件系统属性的不匹配、消息包格式错误、程序设计同步关系错误,以及程序执行中的错误等。差错控制的基本思想与前面的讨论是相似的,例如,在DNA应用层协议DAP中,通过从接收端发送STATUS出错信息包把从接收端发现的错误类型通知给发送端,发送端根据错误性质做重传、忽略处理。发送端也应有超时控制,当它发送完一个文件或一组规定记录后,如果在规定时间收不到STATUS成功确认信息包,也应做相应超时处理以防止死锁。
以上我们讨论了各层差错控制功能的特点,但这并不是一成不变的。随着网络质量的提高,特别是网络传输媒体由电缆改为光缆后,差错控制功能在各层上的分工也发生了比较大的变化。我们以X.25和帧中继(Frame Relay)为例来说明这个问题。
在X.25网络发展初期,网络传输设施基本上借用了模拟电话线路,这种线路非常容易受到噪声的干扰而产生误码。为了确保传输无差错,X.25在每个结点都需要做大量的处理。例如,X.25的数据链路层协议LAPB保证了帧在结点间无差错传输。只有当收到的帧已进行了正确性检查后,才将它交付给第3层协议。对于经历多个网络结点的帧,这种处理帧的方法会导致较长的时延。除了数据链路层的开销,分组层协议为确保在每个逻辑信道上按序正确传送,还要有一些处理开销。在一个典型的X.25网络中,分组在传输过程中在每个结点有30次左右的差错检查或其他处理步骤。
今天的数字光纤网比早期的电话网具有低得多的误码率,因此,我们完全可以简化X.25的某些差错控制过程。如果减少结点对每个分组的处理时间,则各分组通过网络的时延亦可减小,同时结点对分组的处理能力也就增大了。
帧中继就是一种减少结点处理时间的技术。帧中继的原理很简单,即:我们不妨认为帧的传送基本上不会出错,因此只要一知道帧的目的地址就立即开始转发该帧。也就是说,一个结点在接收到帧的首部后,就立即开始转发该帧的某些部分。这样,在一个帧中继网络中,一结点在收到一个帧时,大约只需执行6个检错步骤。这显然减小了帧在结点的时延。实验结果表明,采用帧中继时一个帧的处理时间可以减少一个数量级。这种传输数据的帧中继方式也称为X.25的流水线方式,但帧中继网络的吞吐量却要比X.25网络的提高一个数量级以上。
帧中继使用的这种方法的一个明显问题就是若出现差错该如何处理。按上述方法,只有当整个帧被收下后结点才能够检测到比特差错。但是当该结点检测出差错时,很可能该帧的大部分已经转发到了下一个结点。
解决这一问题的方法实际上非常简单。当检测到有误码的结点时要立即中止这次传输。当中止传输的指示到达下个结点后,下一个结点就立即中止该帧的传输,并丢弃该帧。即使上述出错的帧已到达了目的结点,用这种丢弃出错帧的方法也不会引起不可弥补的损失。不管是上述的哪一种情况,源站将用较高层的协议请求重传该帧。帧中继网络纠正一个比特差错所用的时间要比传统的分组交换网稍多一些。因此,仅当帧中继网络本身的误比特率非常低时,帧中继技术才是可行的。
另外的一个例子是异步传递方式(ATM,Asynchronous Transfer Mode)中的差错控制机制。ATM是建立在电路交换和分组交换的基础上的一种新的交换技术。与帧中继相类似,由于光纤信道的误码率极低,且容量很大,因此在ATM网内不必在数据链路层进行差错恢复和流量控制,而是放在高层处理,因而明显地提高了ATM的信元在网络中的传送速率。有关ATM体系结构中的各种协议的差错控制方法见参考文献[10]。