计算机网络第三章笔记(差错检测)

201 阅读14分钟

差错检测

现实的通信链路都不会是理想的。这就是说,比特在传输过程中可能会产生差错:1 可 能会变成0,而0也可能变成1。

这就叫做比特差错。比特差错是传输差错中的一种。本小节 所说的“差错”,如无特殊说明,就是指“比特差错”。

在一段时间内,传输错误的比特占所 传输比特总数的比率称为误码率BER (Bit Error Rate)。

例如,误码率为10-10时,表示平均每 传送1010个比特就会出现一个比特的差错。

误码率与信噪比有很大的关系。如果设法提高信 噪比,就可以使误码率减小。实际的通信链路并非是理想的,它不可能使误码率下降到零。

因此,为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用各种差错检测措施。

目前在数据链路层广泛使用了循环冗余检验 CRC(Cyclic Redundancy Check)的检错技术。

差错检测是在通信过程中用于检测和识别传输中可能出现的比特差错的技术。在实际的通信链路中,比特在传输过程中可能由于噪声、干扰或其他原因发生变化,即从1变为0或从0变为1,这就是比特差错。

误码率(Bit Error Rate,BER)是衡量传输中出现差错的频率的指标。它表示在传输的比特中,传输错误的比特占总传输比特数的比率。例如,误码率为10^-10时,表示平均每传送10^10个比特会出现一个比特的差错。

通信链路的质量往往与信噪比(Signal-to-Noise Ratio,SNR)有关。通过提高信噪比,可以减小误码率,提高数据传输的可靠性。

在计算机网络中,在数据链路层使用循环冗余检验(Cyclic Redundancy Check,CRC)作为一种常见的差错检测技术。CRC是一种多项式运算,通过对数据进行多项式除法并得到余数,将该余数附加到数据中作为冗余校验码。接收方在接收到数据后,进行相同的多项式除法运算,如果余数为0,则说明没有差错发生;如果余数不为0,则说明数据中存在差错。

通过在数据中添加冗余校验码并进行计算,差错检测技术可以帮助发现传输过程中的比特差错,从而提高数据传输的可靠性。

请你举出与其相对应的代码示例。

当涉及到差错检测技术时,我们可以使用循环冗余检验(CRC)来举例说明。以下是一个使用CRC进行差错检测的示例代码:



def crc_remainder(data, divisor):
    # 在数据末尾添加0,以确保能够进行完整的除法运算
    data += "0" * (len(divisor) - 1)

    # 将数据转换为二进制列表
    data = list(data)
    divisor = list(divisor)

    # 进行循环除法运算
    while "1" in data[:len(data) - len(divisor) + 1]:
        # 找到第一个1的位置
        cur_idx = data.index("1")

        # 执行除法运算(异或操作)
        for i in range(len(divisor)):
            data[cur_idx + i] = "1" if data[cur_idx + i] != divisor[i] else "0"

    # 返回余数(冗余校验码)
    remainder = "".join(data)[-len(divisor) + 1:]
    return remainder


def crc_check(data, divisor):
    # 进行一次除法运算并检查余数是否为0
    remainder = crc_remainder(data, divisor)
    return remainder == "0"


# 示例用法
data = "1101101011"  # 待传输的数据
divisor = "1011"  # CRC多项式(生成器)

# 添加冗余校验码到数据中
data_with_crc = data + crc_remainder(data, divisor)

# 模拟传输过程中的差错(将某一位进行取反)
transmitted_data = data_with_crc[:5] + ("0" if data_with_crc[5] == "1" else "1") + data_with_crc[6:]

# 检查传输数据是否存在差错
is_error = not crc_check(transmitted_data, divisor)
print("传输数据是否存在差错:", is_error)

上述代码中,crc_remainder 函数用于计算给定数据和CRC多项式的除法余数(冗余校验码)。crc_check 函数则用于检查传输数据中的冗余校验码是否为0,若为0则表示没有差错。

在示例中,我们构造了一个带有冗余校验码的数据 data_with_crc,然后模拟传输过程中的差错,将第6位进行了取反操作。最后,通过调用 crc_check 函数来检查传输数据是否存在差错。如果传输数据存在差错,is_error 变量的值将为 True,否则为 False

请注意,上述示例只是用于说明差错检测的原理和实现方法,实际的差错检测方案可能会更加复杂和全面。

下面我们通过一个简单的例子来说明循环冗余检验的原理。

在发送端,先把数据划分为组,假定每组 k 个比特。现假定待传送的数据 M = 101001 (k =6)。CRC 运算就是在数据M 的后面添加供差错检测用的n 位冗余码, 然后构成一个帧 发送出去, 一共发送(k+n) 位。在所要发送的数据后面增加n 位的冗余码,虽然增大了数据 传输的开销,但却可以进行差错检测。当传输可能出现差错时,付出这种代价往往是很值得 的。

这 n 位冗余码可用以下方法得出。用二进制的模 2 运 算°进行2”乘 M 的运算,这相当 于在M 后面添加n 个0。得到的(k+n) 位的数除以收发双方事先商定的长度为(n+1) 位的除数P, 得出商是Q 而余数是R(n位,比P 少一位)。关于除数P 下面还要介绍。

在图3-8 所示的例子中, M=101001( 即k=6)。假定除数P=1101( 即n=3)。经模2除法运算后 的结果是:商Q=110101(这个商并没有什么用处),而余数R=001。这个余数R 就作为 冗余码拼接在数据 M 的后面发送出去。

这种为了进行检错而添加的冗余码常称为帧检验序 列 FCS(Frame Check Sequence)。 因此加上FCS 后发送的帧是101001001(即2"M+FCS), 共有(k+n) 位。

顺便说一下,循环冗余检验 CRC 和帧检验序列 FCS 并不是同一个概念。 CRC 是一种 检错方法,而 FCS 是添加在数据后面的冗余码,在检错方法上可以选用 CRC, 但也可不选 用CRC。

感谢您提供的说明。根据您提供的信息,我们可以通过以下代码示例来演示循环冗余检验(CRC)的原理。



def crc_remainder(data, divisor):
    # 在数据末尾添加 0,以便进行除法运算
    data += "0" * (len(divisor) - 1)

    # 将数据转换为二进制列表
    data = list(data)
    divisor = list(divisor)

    # 执行循环除法运算
    while "1" in data[:len(data) - len(divisor) + 1]:
        # 找到第一个 1 的位置
        cur_idx = data.index("1")

        # 执行除法运算(异或操作)
        for i in range(len(divisor)):
            data[cur_idx + i] = "1" if data[cur_idx + i] != divisor[i] else "0"

    # 返回余数(冗余码)
    remainder = "".join(data)[-len(divisor) + 1:]
    return remainder


# 示例用法
data = "101001"  # 待传输的数据
divisor = "1101"  # CRC 多项式(除数)

# 计算余数(冗余码)
remainder = crc_remainder(data, divisor)

# 构建帧(添加冗余码)
frame = data + remainder

print("发送的帧:", frame)

在上述代码中,crc_remainder 函数用于计算给定数据和CRC多项式的除法余数(冗余码)。在示例中,我们使用了您提供的数据和多项式。crc_remainder 函数通过执行循环除法运算,将数据和多项式进行二进制异或操作,直到无法执行更多的除法运算为止。最终,余数(冗余码)被返回。

在示例中,我们计算出了数据 "101001" 和多项式 "1101" 的余数,即冗余码 "001"。然后,我们将冗余码拼接到数据后面,形成了发送的帧 "101001001"。

需要注意的是,CRC和帧检验序列FCS虽然相关,但并不完全相同。CRC是一种检错方法,而FCS则是指添加在数据后面的冗余码。在某些情况下,我们可以选择使用CRC作为检错方法,并将CRC的余数作为FCS添加在数据后面。然而,CRC和FCS并非完全等同,并且可以根据需求进行选择和配置。

这段代码是实现了循环冗余检验(CRC)的功能。下面是代码的具体含义:

  1. crc_remainder函数接受两个参数:data表示待传输的数据,divisor表示CRC多项式(也称为除数)。

  2. 代码的第2行通过在数据末尾添加0,将其长度扩展到与除数相同,以便进行除法运算。

  3. 第5行和第6行将数据和除数转换为二进制列表,方便后续的异或操作。

  4. 第9行至第17行执行循环除法运算。在这个过程中,代码先找到数据中第一个1的位置(第一个被除数的位置),然后执行除法运算(异或操作)。对于数据中的每个1,它与除数进行异或操作,并将结果存储回数据列表中。这样,每一次循环都相当于执行了一次除法运算。

  5. 第20行通过取得数据列表中除去最高位的部分,即除数的长度减1,得到最终的除法余数(冗余码)。

  6. 最后,第21行将冗余码拼接到原始数据后面,形成了发送的帧。

在示例中,我们使用数据 "101001" 和多项式 "1101" 进行了CRC运算,并得到了冗余码 "001"。最后,打印出了发送的帧 "101001001",其中后三位是冗余码。这样,在传输数据时,我们可以将帧中的数据与冗余码一起发送,并在接收端进行CRC校验,以检测传输过程中是否出现错误或数据损坏。

在接收端把接收到的数据以帧为单位进行 CRC 检验:把收到的每一个帧都除以同样的 除数P (模2运算),然后检查得到的余数R。

如果在传输过程中无差错,那么经过 CRC 检验后得出的余数R 肯定是0(读者可以自 己验算一下。被除数现在是101001001,而除数是P=1101, 看余数R 是否为0)。

但如果出现误码,那么余数 R 仍等于零的概率是非常非常小的(这可以通过不太复杂 的概率计算得出,例如,可参考[TANE11])。

总之,在接收端对收到的每一帧经过CRC 检验后,有以下两种情况:

(1)若得出的余数R=0, 则判定这个帧没有差错,就接受(accept)。 (2)若余数 R≠0, 则判定这个帧有差错(但无法确定究竟是哪一位或哪几位出现了差 错),就丢弃。

一种较方便的方法是用多项式来表示循环冗余检验过程。在上面的例子中,用多项式 P(X)=X³+X²+1 表示上面的除数P=1101 (最高位对应于X³, 最低位对应于X)。 多项式 P(X)称为生成多项式。 现在广泛使用的生成多项式P(X) 有以下几种:

CRC- 16=xl⁶+x¹⁵+X²+1 CRC-CCITT=xl⁶+x¹²+X⁵+1 CRC-32=x³²+x²⁶+x²³+x²²+x¹⁶+x¹²+x¹¹+x¹⁰+x⁸+X⁷+x⁵+x⁴+x²+X+1

在数据链路层,发送端帧检验序列 FCS 的生成和接收端的 CRC 检验都是用硬件完成 的,处理很迅速,因此并不会延误数据的传输。

从以上的讨论不难看出,如果我们在传送数据时不以帧为单位来传送,那么就无法加 入冗余码以进行差错检验。因此,如果要在数据链路层进行差错检验,就必须把数据划分为 帧,每一帧都加上冗余码, 一帧接一帧地传送,然后在接收方逐帧进行差错检验。

最后再强调一下,在数据链路层若仅仅使用循环冗余检验 CRC 差错检测技术,则只能

做到对帧的无差错接受,即:“凡是接收端数据链路层接受的帧,我们都能以非常接近于1的概率认为这些帧在传输过程中没有产生差错”。接收端丢弃的帧虽然曾收到了,但最终还 是因为有差错被丢弃,即没有被接受。以上所述的可以近似地表述为(通常都是这样认为): “凡是接收端数据链路层接受的帧均无差错”。

在接收端,对于每个接收到的帧,可以进行CRC检验以验证传输过程中是否出现了错误。以下是对该检验过程的解释:

  1. 首先,我们需要知道使用的生成多项式,例如CRC-16、CRC-CCITT或CRC-32。生成多项式表示了使用的除数。

  2. 收到的每个帧都包含了数据和冗余码(CRC校验位)。

  3. 接收端需要使用相同的生成多项式进行CRC计算。

  4. 对于每个收到的帧,将帧中的数据与接收端所用的生成多项式进行除法运算(模2运算),得到余数R。

  5. 如果余数R为0,则判断该帧没有差错,可以接受(accept)。

  6. 如果余数R不等于0,则判断该帧存在差错,无法确定具体是哪一位或哪几位出错,因此丢弃该帧。

使用这种方法,我们可以判定接收到的每一帧是否存在错误,但无法确定具体出错的位置。这种方法能够以非常接近于1的概率认为没有差错的帧在传输过程中没有产生差错。但需要注意的是,丢弃的帧虽然之前已经接收到,但最终还是因为存在差错而被丢弃,即没有被接受。

总结起来,仅使用循环冗余检验(CRC)差错检测技术,数据链路层只能做到对帧的无差错接受。也就是说,凡是接收端数据链路层接收的帧,我们可以以非常接近于1的概率认为这些帧在传输过程中没有产生差错。丢弃的帧虽然之前已经接收到,但最终还是因为存在差错而被丢弃,即没有被接受。

请你举出与其相对应的代码示例。

以下是一个简单的代码示例,演示了接收端如何进行CRC检验以验证接收到的帧是否有差错。

# 定义生成多项式
divisor = 0b1101  # P=1101

# 接收到的帧
received_frame = 0b101001001


def crc_check(received_frame, divisor):
    # 将接收帧左移,为CRC计算指定的位数(除数的位数-1),并追加0作为余数位
    frame_with_remainder = received_frame << (len(bin(divisor)) - 3)

    # 进行循环计算
    while len(bin(frame_with_remainder)) >= len(bin(divisor)):
        # 计算当前帧与除数的模2运算
        quotient = frame_with_remainder // divisor
        remainder = frame_with_remainder ^ (quotient * divisor)
        frame_with_remainder = remainder << 1

    # 判断余数是否为0
    if frame_with_remainder == 0:
        return True  # 帧没有差错
    else:
        return False  # 帧有差错


# 调用CRC检验函数
result = crc_check(received_frame, divisor)

if result:
    print("帧没有差错,可以接受。")
else:
    print("帧有差错,丢弃。")

这个代码示例中,我们首先定义了生成多项式(除数)和接收到的帧。然后定义了一个 crc_check 函数,它接收接收到的帧和生成多项式作为输入,实现了CRC检验的算法。最后,我们调用这个函数并根据返回的结果输出相应的信息。

请注意,这只是一个简化的示例,实际的CRC实现可能会更复杂,并且可能会使用更高效的算法和数据结构来提高性能。具体的实现方式可能会因使用的编程语言而有所差异。以上示例代码是使用Python编写的。