[技术栈]CRC校验原理及C#代码实现CRC16、CRC32计算FCS校验码
1.CRC、FCS是什么
CRC,全称Cyclic Redundancy Check,中文名称为循环冗余校验,是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
FCS,全称Frame Check Sequence,中文名称为帧校验序列,俗称帧尾,即计算机网络数据链路层的协议数据单元(帧)的尾部字段,是一段4个字节的循环冗余校验码。
注:CRC循环冗余校验和FCS帧校验序列是单独的概念,CRC是一种错误校验方法,FCS是帧尾校验码,FCS可以采用CRC校验方法,也可以采用其他校验方法。
2.CRC算法原理
我们可以把任意的一串二进制数据表示为一个与之对应的多项式。比如:
二进制数据:1100101
多项式:\(x^6 + x^5 + x^2+1\)
多项式: \(x^6 + x^4+x^3 + x^2+1\)
二进制数据:1011101
有了这样的对应关系,对二进制数据的CRC校验就可以利用多项式运算规则进行校验计算。
CRC校验算法正是采用了模2除法,在数据处理里的具体表现为异或运算。
CRC的具体运算规则为:假设要传输的二进制数据为:10010110,对应的m阶多项式为:\(M =x^7+x^4+x^2+x^1\),除数为h阶的多项式为:\(H=x^4+x\),对应的二进制码为:10010,先将M乘以\(x^h\),即将M对应的二进制数据后面加h个0,然后除以h阶的多项式H,得到的h-1阶的余数项R对应的二进制数据即为数据10010110的CRC校验码。
3.计算CRC校验
3.1.手工计算CRC校验码
M和H的多项式除法运算,可以用模2除法运算计算。下面为以生成多项式为H求10010110的CRC校验码运算过程:

对应到异或运算:

通过示例即其他自定义的一些数据运算后,根据运算现象总结可以得到一些规律:
1.每次异或运算,当从左到右首位为1的时候,就与生成多项式H异或运算,然后再左移1位;当首位为0的时候只将数据左移1位。
2.每次异或运算后的数据,首位必定为0,因为首位为1的时候进行异或运算,而生成多项式的首位也必定为1。所以当需要进行异或运算时,可以舍弃H的首位,舍弃后为H',直接将数据左移一位后再与H'异或。

3.每次运算,参与运算的是数据的前h位,可以用一个存储h位二进制数据的寄存器S,将数据的前h位存储到这个寄存器中。每次运算先将寄存器的首位移除,然后将二进制数据后一位移入,然后再参与运算,最后寄存器中的值即为CRC校验码。
3.2.C#代码计算CRC校验码
//代码验证如下:
static void Main(string[] args)
{
int data = 0b10010110;
int ploy = 0b0010;
ploy <<= 4;
Console.WriteLine($"第0次运算结果:"+Convert.ToString(data, 2));
for (int i = 0; i < 8; i++)
{
if ((data & 0b10000000) == 0b10000000)
{
data = (data << 1) ^ ploy;
}
else
{
data <<= 1;
}
Console.WriteLine($"第{i+1}次运算结果:"+Convert.ToString(data, 2));
}
Console.WriteLine($" 最终运算结果:"+Convert.ToString(data, 2));
Console.ReadKey();
}

这里用int的第5位到第8位作为一个四位寄存器,可以看到与手算一致,最后算得校验位1100。
4.查表法

可以看到,参与运算的始终只有4位,所以在移位D1数据时,参与运算的数据只有D1和D2,经过四次移位运算,D1被移除寄存器,这个时候受到影响的只有D2。而将D2的初值经过四次异或运算后的值就可以获得四次移位后的新数据\(D2'=D2\bigoplus H1 \bigoplus H2\bigoplus H3\bigoplus H4 = D2 \bigoplus \sum{h}\)。
每一次D2是异或0还是异或生成多项式H',与D2本身的值无关,仅与D1中被移除的数据有关(首位为0还是1),所以这里引入了一个查表法,即先将所有可能的D1组合都计算出对应的\(\sum{h}\),一次性移除四位,然后以\(D2\bigoplus{\sum{h}}\)即可以获得D2'。
生成多项式为H,则一共有\(2^h\)种可能,代码如下:
class CalcByCrcTable
{
private byte[] CrcTable;
private void CteateTable()
{
int ploy = 0b0010;
CrcTable = new byte[(int)Math.Pow(2,4)];
ploy <<= 4;
for (int i = 0; i < CrcTable.Length ; i++)
{
int data = i<<4;
for (int j = 0; j < 4; j++)
{
if ((data & 0b10000000) == 0b10000000)
{
data = (data << 1) ^ ploy;
}
else
{
data <<= 1;
}
}
CrcTable[i] = Convert.ToByte((data & 0xf0)>>4);
}
}
public byte CalcCrc()
{
CteateTable();
int data = 0b10010110;
byte crchigh4 = CrcTable[(data>>4)&0x0f];//用查表法先查到data的高四位1001的crc值;
byte value = Convert.ToByte((data & 0x0f) ^ crchigh4);//将高四位的CRC与低四位异或,得到移位四次后的数据值;
byte crc = CrcTable[value]; //在用移位后的数据值查出数据的CRC校验码;
return crc;
}
}
static void Main(string[] args)
{
CalcByCrcTable calcByCrcTable = new CalcByCrcTable();
byte crc = calcByCrcTable.CalcCrc();
Console.WriteLine($" CRC校验码为:" + Convert.ToString(crc, 2));
Console.ReadKey();
}
//打印结果如下
CRC校验码为:1100
可以看到与前面的计算法结果一致。
5.反向校验
上面所诉的均为正向检验(Normal),当然也有反向校验(Reversed),反向校验是将数据和生成多项式均进行了一个镜像,当然算法也需要镜像,即镜像后从右往左运算。
5.1手工计算CRC反向校验码
原二进制数据:10010110
原生成多项式:0010
正向CRC校验码:1100
镜像二进制数据:01101001
镜像生成多项式:0100
镜像算法:

反向CRC校验码:0011
5.2.C#代码计算CRC反向校验码
class CalcByCrcTable
{
private byte[] CrcTable;
private void CteateReversedTable()
{
int ploy = 0b0100;
CrcTable = new byte[(int)Math.Pow(2, 4)];
for (int i = 0; i < CrcTable.Length; i++)
{
int data = i;
for (int j = 0; j < 4; j++)
{
if ((data & 1) == 1)
{
data = (data >> 1) ^ ploy;
}
else
{
data >>= 1;
}
}
CrcTable[i] = Convert.ToByte((data & 0x0f));
}
}
public byte CalcReversedCrc()
{
CteateReversedTable();
int data = 0b01101001;
byte crclow4 = CrcTable[data & 0x0f];//用用查表法先查到data的低四位1001的crc值;
byte value = Convert.ToByte(((data>>4) & 0x0f) ^ crclow4);//将第四位的CRC与低四位异或,得到移位四次后的数据值;
byte crc = CrcTable[value]; //在用移位后的数据值查出数据的CRC校验码;
return crc;
}
}
static void Main(string[] args)
{
CalcByCrcTable calcByCrcTable = new CalcByCrcTable();
byte crc = calcByCrcTable.CalcReversedCrc();
Console.WriteLine($" CRC反向校验码为:" + Convert.ToString(crc, 2));
Console.ReadKey();
}
//打印结果如下
CRC反向校验码为:11
6.C#查表法计算CRC16校验码
//多线程使用时请注意干扰
class CalcOnCrc16
{
private ushort[] Crc16NormalTable;
private ushort[] Crc16ReversedTable;
private void CreateNormalCrc16Table(ushort ploy)
{
ushort data;
Crc16NormalTable = new ushort[256];
int i, j;
for (i = 0; i < 256; i++)
{
data = (ushort)(i << 8);
for (j = 0; j < 8; j++)
{
if ((data & 0x8000) == 0x8000)
data = Convert.ToUInt16((ushort)(data << 1) ^ ploy);
else
data <<= 1;
}
Crc16NormalTable[i] = data;
}
}
private void CreateReversedCrc16Table(ushort ploy)
{
ushort data;
Crc16ReversedTable = new ushort[256];
int i, j;
for (i = 0; i < 256; i++)
{
data = (ushort)i;
for (j = 0; j < 8; j++)
{
if ((data & 1) == 1)
data = Convert.ToUInt16((ushort)(data >>1) ^ ploy);
else
data >>= 1;
}
Crc16ReversedTable[i] = data;
}
}
/// <summary>
/// 正向计算CRC16校验码
/// </summary>
/// <param name="bytes">校验数据</param>
/// <param name="poly">生成多项式</param>
/// <param name="crcInit">校验码初始值</param>
/// <returns></returns>
public ushort CalcNoemalCrc16(byte[] bytes,ushort poly,ushort crcInit)
{
CreateNormalCrc16Table(poly);
ushort crc = crcInit;
for (int i = 0; i < bytes.Length; i++)
{
crc = Convert.ToUInt16((ushort)(crc << 8) ^ Crc16NormalTable[((crc >> 8) & 0xff) ^ bytes[i]]);
}
return crc;
}
/// <summary>
/// 反向计算CRC16校验码
/// </summary>
/// <param name="bytes">校验数据</param>
/// <param name="poly">反向生成多项式</param>
/// <param name="crcInit">校验码初始值</param>
/// <returns></returns>
public ushort CalcReversedCrc16(byte[] bytes, ushort poly, ushort crcInit)
{
CreateReversedCrc16Table(poly);
ushort crc = crcInit;
for (int i = 0; i < bytes.Length; i++)
{
crc = Convert.ToUInt16((ushort)(crc >> 8) ^ Crc16ReversedTable[(crc & 0xff) ^ bytes[i]]);
}
return crc;
}
}
7.C#查表法计算CRC32校验码
class CalcOnCrc32
{
private uint[] Crc32NormalTable;
private uint[] Crc32ReversedTable;
private void CreateNormalCrc32Table(uint ploy)
{
uint data;
Crc32NormalTable = new uint[256];
int i, j;
for (i = 0; i < 256; i++)
{
data = (uint)(i << 24);
for (j = 0; j < 8; j++)
{
if ((data & 0x80000000) == 0x80000000)
data = Convert.ToUInt32((uint)(data << 1) ^ ploy);
else
data <<= 1;
}
Crc32NormalTable[i] = data;
}
}
private void CreateReversedCrc32Table(uint ploy)
{
uint data;
Crc32ReversedTable = new uint[256];
int i, j;
for (i = 0; i < 256; i++)
{
data = (uint)i;
for (j = 0; j < 8; j++)
{
if ((data & 1) == 1)
data = Convert.ToUInt32((uint)(data >> 1) ^ ploy);
else
data >>= 1;
}
Crc32ReversedTable[i] = data;
}
}
/// <summary>
/// 正向计算CRC32校验码
/// </summary>
/// <param name="bytes">校验数据</param>
/// <param name="poly">生成多项式</param>
/// <param name="crcInit">校验码初始值</param>
/// <returns></returns>
public uint CalcNoemalCrc32(byte[] bytes, uint poly, uint crcInit)
{
CreateNormalCrc32Table(poly);
uint crc = crcInit;
for (int i = 0; i < bytes.Length; i++)
{
crc = Convert.ToUInt32((uint)(crc << 8) ^ Crc32NormalTable[((crc >> 24) & 0xff) ^ bytes[i]]);
}
return crc;
}
/// <summary>
/// 反向计算CRC32校验码
/// </summary>
/// <param name="bytes">校验数据</param>
/// <param name="poly">反向生成多项式</param>
/// <param name="crcInit">校验码初始值</param>
/// <returns></returns>
public uint CalcReversedCrc32(byte[] bytes, uint poly, uint crcInit)
{
CreateReversedCrc32Table(poly);
uint crc = crcInit;
for (int i = 0; i < bytes.Length; i++)
{
crc = Convert.ToUInt32((uint)(crc >> 8) ^ Crc32ReversedTable[(crc & 0xff) ^ bytes[i]]);
}
return crc;
}
}
参考资料
[技术栈]CRC校验原理及C#代码实现CRC16、CRC32计算FCS校验码的更多相关文章
- JHipster技术栈理解 - UAA原理分析
本文简要分析了UAA的认证机制和部分源码功能. UAA全称User Account and Authentication. 相关源码都是通过Jhipster生成,包括UAA,Gateway,Ident ...
- CRC的校验原理
一.基本原理 CRC检验原理实际上就是在一个p位二进制数据序列之后附加一个r位二进制检验码(序列),从而构成一个总长为n=p+r位的二进制序列:附加在数据序列之后的这个检验码与数据序列的内容之间存在着 ...
- CRC码计算及校验原理的最通俗诠释
5.3.2 循环冗余校验检错方案 奇偶校验码(PCC)只能校验一位错误,本节所要介绍的循环冗余校验码(CRC)的检错能力更强,可以检出多位错误. 1. CRC校验原理 CRC校验原理看起来比 ...
- CRC 校验原理及步骤
什么是 CRC 校验? CRC 即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计 ...
- CRC校验原理及步骤
什么是CRC校验? CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并 ...
- CRC校验原理
此文为转载文,原作者博客传送门 CRC校验原理 CRC校验原理看起来比较复杂,好难懂,因为大多数书上基本上是以二进制的多项式形式来说明的.其实很简单的问题,其根本思想就是先在要发送的帧后面附加一个数( ...
- iOS 覆盖率检测原理与增量代码测试覆盖率工具实现
背景 对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久.如果在开发.测试阶段能够提前暴露问题,就有助于避免线上事故的发生.代码覆盖率检测正是帮助开发.测试同学提前发现 ...
- 通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core? .Net Web开发技术栈
通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core? 什么是.NET?什么是.NET Framework?本文将从上往下,循序渐进的介绍一系列相关.NET的概念 ...
- Java 技术栈中间件优雅停机方案设计与实现全景图
欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...
随机推荐
- 使用okHttp登录、Md5密码加密
1.使用okHttp3登录 2.Md5密码加密 3.完整代码 4.项目案例 使用okHttp3登录: 使用okHttp3之前要在build.gradle引入okHttp3的依赖(顺便引入解析数据的gs ...
- java中的case里嵌套if条件句; 输入一个年份的某一个月份,判断这个月有多少天
public class year { public static void main(String arg[]){ Scanner a=new Scanner(System.in ...
- VScode 插件推荐与C/C++配置
以下是我经常用到的VScode插件.由于插件本身具有详细的配置和介绍,不对插件本身的安装配置进行说明,仅仅支出这些插件的主要功能.具体使用强烈推荐看一下安装插件后的说明,大多数的问题和设置都可以找到, ...
- C#开发BIMFACE系列10 服务端API之获取文件下载链接
系列目录 [已更新最新开发文章,点击查看详细] 通过BIMFACE控制台或者调用服务接口上传文件成功后,默认场景下需要下载该源文件,下载文件一般需要知道文件的下载链接即可.BIMACE平台提供 ...
- HTTP协议请求响应模型
HTTP协议请求响应模型:以”用户登录“这个场景来描述 第一步:客户端发起请求到API接口层,操作:用户在客户端填写用户名和密码,点击登录,发送请求. 第二步:api接收到客户端发起的用户请求,api ...
- poj - 1860 Currency Exchange Bellman-Ford 判断正环
Currency Exchange POJ - 1860 题意: 有许多货币兑换点,每个兑换点仅支持两种货币的兑换,兑换有相应的汇率和手续费.你有s这个货币 V 个,问是否能通过合理地兑换货币,使得你 ...
- 题解 bzoj 2151 种树
题意 传送门 手写堆大法好啊,题解貌似没有结构体堆的做法,思路有些像配对堆,关于配对堆请自行百度,因为本蒟蒻不会.. 以下是蒟蒻的做法:建立一个大根堆a维护最大价值里面存入它的编号以及价值.听说配对堆 ...
- 深度递归必须知道的尾调用(Lambda)
引导语 本文从一个递归栈溢出说起,像大家介绍一下如何使用尾调用解决这个问题,以及尾调用的原理,最后还提供一个解决方案的工具类,大家可以在工作中放心用起来. 递归-发现栈溢出 现在我们有个需求,需要计算 ...
- 逆向破解之160个CrackMe —— 030
CrackMe —— 030 160 CrackMe 是比较适合新手学习逆向破解的CrackMe的一个集合一共160个待逆向破解的程序 CrackMe:它们都是一些公开给别人尝试破解的小程序,制作 c ...
- js操作cookie(创建、读取、删除)方法总结
js操作cookie,可以通过开源的插件实现,方便快捷,兼容性好,同样也可以自己写: 此文主要介绍两个常用的插件: Js.cookie.js 和 jQuery.cookie.js 0.Js.cooki ...