CRC码计算及原理(FCS帧校验序列生成)

我们知道在以太网帧的末尾有一个叫FCS的东西。
全称:Frame Check Sequence,中文名:帧检验序列
这个东西是用来检验我们的数据是否在传输的过程中被破坏(不一定是收到攻击,也可能是一些物理干扰),以更好的安排重发。
而其中最常用的,也是检错能力很强的,就是CRC,循环冗余校验码。

操作流程

一个小背景知识

模二除法,或者说在数域{1,0}上的除法。
与普通除法类似,它也可以列竖式计算,但是唯一不同的,是相减的那一步。
我们这里的除法,在相减时,遵循一下规则:

  1. 1-1=0
  2. 1-0=1
  3. 0-1=1
  4. 0-0=0
  5. 不进位,也不借位

举个例子。
CRC码计算及原理(FCS帧校验序列生成)_第1张图片
我相信应该很清楚了。(手画的,不容易)

生成

首先,我们需要一个除数,这个除数可以按照某个行业标准来,比如:

IBM的SDLC(同步数据链路控制)规程中使用的CRC-16为:11000000000000101,在ISO HDLC(高级数据链路控制)规程、ITU的SDLC、X.25、V.34、V.41、V.42等中使用CCITT-16为:11000000000100001。

当然这里做个实验,我们也可以随机生成,但是有一点要求,最高位和最低为必须为1,这一点需要注意。

接下来,我们把数据左移(k-1)位,补零,这就是我们的被除数。
然后用这个被除数和除数做模二除法得到余数。
这个余数(k-1位,不足的话,左侧补零),就是加在数据末尾的FCS。
然后我们把它加在数据末尾即可。

校验

那么我们如何校验呢?
直接把我们得到的串(包含FCS),对除数再做一次模二除法,如果余数是零,则说明数据完好。如果不为0,则说明数据遭到破坏,需要安排重发。

代码实现

相信看了上面的内容,应该很容易实现代码,下面给出我写的代码:

#include
#include
#include
#include
#include
using namespace std;
struct bin{//二进制数据 
	int num[100000];
	int len;
	void init(){//初始化为0 
		len=1;
		memset(num,0,sizeof(num));
	}
	void read(){//读入数字 
		char c[100000];
		scanf("%s",c);
		len=strlen(c);
		for(int i=0;i<len;i++){
			num[i]=c[len-i-1]-'0';
		}
	}
	void clean(){//清除前导0 
		for(int i=99999;i>=0;i--){
			if(num[i]==1){
				len=i+1;
				return;
			}
		}
		len=1;
	}
	void gen(int k){//随机生成 
		len=k;
		num[len-1]=num[0]=1;
		for(int i=1;i<len-1;i++){
			num[i]=rand()%2;
		}
	}
	bin operator =(bin b){//赋值符号 
		len=b.len;
		for(int i=0;i<len;i++){
			num[i]=b.num[i];
		}
		return *this;
	}
	bin operator +(bin b){//加法 
		bin c;
		c.init();
		c.len=max(b.len,len);
		for(int i=0;i<c.len;i++){
			c.num[i]=num[i]+b.num[i];
		}
		return c;
	}
	bin operator <<=(int b){//左移 
		for(int i=len-1;i>=0;i--){
			num[i+b]=num[i];
			num[i]=0;
		}
		len+=b;
		return *this;
	}
	bin operator /(bin b){//除法取余数(返回值为余数) 
		bin c;
		c.init();
		c=*this;
		for(int i=c.len-1;i>=0&&i>=(b.len-1);i--){
			if(!c.num[i]){
				continue;
			}
			for(int j=i;j>i-b.len;j--){
				c.num[j]-=b.num[j-i+b.len-1];
				if(c.num[j]<0){
					c.num[j]=1;
				}
			}
		}
		c.clean();
		return c;
	}
	int operator ==(bin b){//等于号 
		if(b.len!=len){
			return 0;
		}
		for(int i=0;i<len;i++){
			if(num[i]!=b.num[i]){
				return 0;
			}
		}
		return 1;
	}
	void print(){//打印 
		for(int i=len-1;i>=0;i--){
			printf("%d",num[i]);
		}
		printf("\n");
	}
};
int main(){
	//设定k值 
	int k=5;
	srand(time(0));
	//定义各种量 
	bin text,key,div,txt,zero;
	//初始化 
	div.init();
	key.init();
	txt.init();
	zero.init();
	text.init();
	//生成 
	text.read();//读入数据 
	key.gen(5);//生成除数 
	txt=text; 
	txt<<=(k-1);//数据左移 
	div=txt/key;//取余数 (FCS) 
	txt=txt+div;// 合成 
	//模拟破坏  
//	txt.num[txt.len/2]^=1;
	//校验
	txt=txt/key;
	if(txt==zero){
		printf("The data are OK!");
	} else{
		printf("The data are fault!");
	}
	return 0;
} 

还可以完善的,我就不进一步完善了。

一些数学原理

之前有说数学背景,但是那只是为了能理解其过程,但并不能让我们明白为什么是这样。
所以这里扩展了一块内容,根据兴趣阅读一下吧!

生成多项式

不知道怎么说,就举例说明吧。

在这里插入图片描述
其对应的除数分别为:

  1. 11000000000000101
  2. 10001000000100001
  3. 100000100110000010001110110110111

我相信你应该明白了。

为什么余数为0

我们刚刚介绍了模二除法。
这里我们再说一个模二加法。

  1. 1+1=0
  2. 1+0=1
  3. 0+0=0
  4. 不进位

为什么是这样定义?
因为模二。
所以你只要让结果对2取模,你就知道为什么会这样了。
我们设原数据左移后为t,除数为a,商为s,余数为r。
所以有:t=as+r
而我们传递的是t+r
即t+r=a
s+r+r
有什么问题?
我们看一下r+r
注意这里是模二加法,不进位,且1+1=0+0=0,
而r和r是一样的,所以模二相加后,就是0!
所以式子变为:t+r=a*s
这样的话(t+r)模二除以a,自然余数为0。
这就是为什么我们可以这么做。

校验成功一定没错吗?

不一定!
有可能会出现某几位错了,但是判断结果是对的。
那么概率是多少呢?
假设FCS为32位(4字节)
数据我们取为8000位(1000字节,此处数据大小取了一个中值,以太网帧中的数据通常为46~1500字节)(当然了,其实从后面的结果可以知道此处并无影响)
8000位数据,最大为8000个1,最小为1(7999个0)。
进行二进制除法,商最大为7969位,总共有27969个可能的商。
总共可能破坏的结果为28000.
所以误判的概率为P=(27969-1)/28000
也就约为1/231,即1/2,147,483,648
这概率是多大呢?
差不多是生一个六胞胎的概率吧。
想一想,你身边有几个六胞胎?你听过几个六胞胎?
因此,其检错能力是很强的。重点是算法也很简单,方便实际应用。

结束语

这就是今天对CRC的探究,喜欢的话点一个赞呗!
有什么问题也欢迎与我讨论!

你可能感兴趣的:(网络)