数据链路层的检错技术——循环冗余校验CRC(Cyclic Redundancy Check)

简介

背景

为了应对现实中的通信链路在传输过程中可能会产生比特差错(误码率,BER——Bit Error Rate,比如误码率为10-10表示平均没传送1010比特就会出现一个比特的差错,1可能变成0,0可能变成1;当然误码率与信噪比有很大关系,提高信噪比可降低误码率。然而实际的通信链路并非理想的,它不可能使误码率下降到0),为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用各种差错检测措施。目前在数据链路层广泛使用了CRC的检错技术。

原理(以CRC-16/XMODEM为例)

CRC校验的原理是根据接收端与发送端共同约定的除数P,在要发送的数据末尾补上一定位数的CRC校验码,以使“要发送数据+校验码”能够被共同约定的除数P以异或^运算的方式整除,接收端以此作为数据在传输过程中是否发生比特差错的判断依据。这个【使得要发送数据可以被共同约定的除数P整除的】CRC校验码被称为帧检验序列FCS(Frame Check Sequence)。P通常用多项式表示,比如CRC-16的G(x)=x^16^ +x^15^+x^2^+1表示除数P为11000000000000101(共17位)。

异或运算就是教科书中提到的“二进制的模2运算”

此处可以看到CRC与FCS不是同一个概念,CRC是一种检错方法,而FCS是添加在要发送数据后面、用来校验的冗余码

补入此博客中的CRC校验原理

应用场景

  • 以太网中使用的是crc-32。

CRC各版本及反转、初始值含义详解

补入本文
初始值:是指【被除数(即需要添加CRC校验码的原数据在补0后)与初始值】先进行异或运算,再进行下面的crc计算。比如初始值为0xFFFF时,相当于将【需要添加CRC校验码的原数据】进行取反码操作。

计算过程

计算过程概述

在发送端,先把要发送的数据划分为,假定每组k个比特。假定待发送的数据 M = 101001 (k = 6),CRC就是根据【发送方与接收方共同约定的】除数P(n+1位)生成【用来差错检测用的n位冗余码(FCS)】,并在待发送数据M后添加FCS,由此构成一个新的(k+n)位的帧,使其能够被除数P整除。发送端将新的(k+n)位帧发送出去,接收端在收到后,对每一个收到的(k+n)位帧进行CRC校验:把收到的每一个帧与共同约定的除数P进行异或运算1,然后检查得到的余数R是否为0。若为0,则说明无差错;不为0,则说明有差错。

n位冗余码的具体计算过程

  1. 根据G(x)得到约定的除数P,除数P是n+1位。比如G(x)=x8 +x6+x5+1 表示除数P是 101100001,请注意,根据G(x)得到除数P,是从0位开始算——即多项式最后的1,表示除数的0位是1而非0。
  2. 根据待发送的数据M和第一步中【根据G(x)得到的除数P】,得出校验码。具体计算过程如下
    ①假设待发送的数据M的二进制位数为k位,除数P二进制位数为n+1位,则在待发送的数据M后面增添n位“0”——即二进制的模2运算——M*2n,形成新的k+n位数据M’。M’取前【与初始值相同位数的】位与初始值进行异或操作,比如初始值有16位,则从M’中取前16位(可包含数据M在后面增添的0部分)。
    ②然后将k+n位的M’异或除以【发送和接收方共同约定的】除数P(n+1位),请注意,此处用代码实现时,不能直接简单地M'^P,详见此批注1和下方给出的代码实现。将【所得到的余数R(二进制的形式,n位)添加到M’增添的后n位0上——比如余数为1,M’在第①步中预留的校验码(全0)位数是三位,则在M’预留的后三位校验码上应改为001,而非1——001称之为FCS(帧校验序列)】,即关注余数FCS添加到M’时的补0问题——将FCS填加到k+n位的M’的n位“0”上。但要注意的是,所得校验码全为0时(即整除)也都不能省略。
  3. 此时,将 M’后n位已更新为FCS的新数据【2nM+FCS】作为要发送数据发出,接收端收到后检查是否其能够被共同约定的除数P(二进制的模2运算)异或1整除。若能够整除,则判定校验通过,数据无误;不能整除,则判定校验不通过,数据在传输过程中出现了差错。

代码实现

Java

实现CRC-16/XMODEM

import java.math.BigInteger;

public class CrcCheckCodeGenerator {
	
	 BigInteger Dividend;//CRC被除数(要加校验码的字串),为了防止位数过长使用的BigInteger
	 BigInteger CRC_Divisor;//CRC除数
	 BigInteger initialValue;//初始值
	 
	 public CrcCheckCodeGenerator(String Dividend, String binaryDivisor, String initialValue) {
	  // TODO Auto-generated constructor stub
	  
	  int fillingZeroNumber = binaryDivisor.length()-1;
	  for(int i = 0; i<fillingZeroNumber; i++) {
	   Dividend = Dividend+"0";
	  }
	  
	  根据初始值截取Dividend

	  this.Dividend = new BigInteger(Dividend,2);
	  this.CRC_Divisor = new BigInteger(binaryDivisor,2);
	 }
	 
	 public String generateCrcCheckCode() {
		 
		 String CRC_DivisorString = CRC_Divisor.toString(2);
		 int binaryResultBit = CRC_DivisorString.length()-1;
		 String binaryResult = "";//CRC最终校验的结果
		 
		 if (Dividend!=null && CRC_Divisor!=null) {
			 String dividendString = Dividend.toString(2);
			 int cRC_Divisor = Integer.parseInt(CRC_DivisorString,2);
			 int i = 0;//代表从Dividend运算到了第几位
			 while (i<dividendString.length()||(binaryResult.length()==CRC_DivisorString.length())) {
				 if (binaryResult.length()<= binaryResultBit) {
					 binaryResult += dividendString.charAt(i)+"";
					 ++i;
				 }else {
					 int result = Integer.parseInt(binaryResult, 2);
					 result = result^cRC_Divisor;
					 if (result == 0) {//防止异或整除后,依然保留一位0
						 binaryResult = "";
					 }else {
						 binaryResult = Integer.toBinaryString(result);
					 }
				
				 }
			 }
			 //补齐最终结果的位数
			 while(binaryResult.length() < CRC_DivisorString.length()-1) {
				 binaryResult = "0"+binaryResult;
			 }
			 return binaryResult;
//			 resultCrcChekCode = Dividend.xor(CRC_Divisor);
	  }else if (Dividend == null) {
		System.out.println("要添加CRC校验码的待发送数据为空");
		binaryResult = "0".repeat(binaryResultBit);
		return binaryResult;
	  }else {
		System.out.println("CRC计算中要用到的除数为空");
		binaryResult = "0".repeat(binaryResultBit);
		return binaryResult;
	  }
	 }
}

实现CRC-16/CCITT-FALSE

public class CrcCheckCodeGenerator {
	 
	String Dividend;
	String CRC_Divisor;
	String initial_Value;
	 
	 //为CRC-16/CCITT-FALSE版本的构造函数,G(x)=x^16+x^12+x^5+1,除数、初始化均直接在构造函数中完成
	public CrcCheckCodeGenerator(String Dividend) {
	   	// TODO Auto-generated constructor stub
		 
	  	//CRC-16/CCITT-FALSE版本的CRC初始值为0xFFFF,Dividend前同位数与其^,相当于取反操作。
		int initial_Value_Int = 0xFFFF;
		Dividend = Dividend + "0000000000000000";
		int firstHalfofDividend = Integer.parseInt(Dividend.substring(0, 16), 2);
		firstHalfofDividend = firstHalfofDividend ^ initial_Value_Int;
		Dividend = Integer.toBinaryString(firstHalfofDividend)+Dividend.substring(16);
		
	  	this.Dividend = Dividend;
	  	this.CRC_Divisor = "10001000000100001";
	 }
	 
	 //配合CRC-16/CCITT-FALSE 使用,除数CRC_Divisor字符串写死无法更改。
	 public String generateCrcCheckCodeForDRO() {
		 
		 int binaryResultBit = 16;//因为专用于CRC-16/CCITT-FALSE,所以CRC校验码的最终位数可以确定
		 String binaryResult = "";//用于存储从Dividend逐个取出用来逐步计算CRC校验码、最终结果的变量
		 
		 if (Dividend!=null && CRC_Divisor!=null) {
		  
			 //需要计算CRC的字符串存为String变量,从中截取补位继续^运算。
//			 String dividendString = Dividend.toString(2);
			 
			 int cRC_Divisor = Integer.parseInt(CRC_Divisor,2);//String转Int
			 int i = 0;//代表从Dividend运算到了第几位
			 while (i<Dividend.length()||(binaryResult.length()==17)) {//在取完被除数Dividend的所有位后,如果binaryResult位数还足够17位、则再与除数CRC_Divisor进入一次循环来计算异或结果;不足17位时,则此时的binaryResult直接就是CRC的校验结果。
			 
				 if (binaryResult.length() <= binaryResultBit) {//因为binaryResult的长度是从0开始的,而除数P是17位,binaryResultBit已经在除数的基础上减1了,所以最后一次计算余数时,是 = 。
					 binaryResult += Character.toString(Dividend.charAt(i));
					 ++i;
				 }else {
					 //
					 int result = Integer.parseInt(binaryResult, 2);
					 result = result^cRC_Divisor;
//					 resultCrcChekCode = Dividend.xor(CRC_Divisor);//此方法的异或取值仅支持4位^4位
					 if (result == 0) {//防止异或整除后依然保留一位0的情况。
						 binaryResult = "";
					 }else {
						 binaryResult = Integer.toBinaryString(result);
					 }
				 }
			 }
			 //自动补齐位数,直到16位停止
			 while(binaryResult.length() < 16) {
				 binaryResult = "0"+binaryResult;
			 }

	  }else if (Dividend == null) {
		System.out.println("要添加CRC校验码的待发送数据为空");
		
		String nullDataString = "0";
		binaryResult = nullDataString.repeat(binaryResultBit);//即最终输出binaryResultBit个nullDataString

	  }else {
		System.out.println("CRC计算中要用到的除数为空");
		
		String nullDataString = "0";
		binaryResult = nullDataString.repeat(binaryResultBit);
	  }
		 
		 return binaryResult;
		 
	 }
}

CRC在线计算器

分享一个CRC多版本在线计算器


  1. 此处所进行的异或运算,依然要等位进行——从收到的帧中取出前n+1位,与【发送方与接收方共同约定的】除数P(n+1位)进行异或运算,得到的余数继续从收到的帧中依次取数补齐n+1位来与除数P进行异或运算。 ↩︎ ↩︎ ↩︎

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值