目录
1. 背景介绍
2. CRC 校验的三种方法
2.1. 直接计算 CRC 校验
2.2. 查短表法计算 CRC16 校验
2.3. 查大表法计算 CRC16 校验
3. 三种校验方式的测试方法
3.1. 直接计算 CRC 校验的时间测试
3.2. 查短表计算 CRC 校验的时间测试
3.3. 查长表计算 CRC 校验的时间测试
4. 校验结果的测试
4.1. CRC 静态帮助类中的校验结果方法
4.2. CRC 验证方法的顶层调用
5. 不同校验方式的性能差异
6. 结果输出
7. 小结
1. 背景介绍
主要应用场景在物联网中, 底端设备注册报文的上报, 需要对报文的有效载荷 (data) 进行 CRC16 的复验, 验证与设备端的 CRC 校验是否相等, 如果相等, 报文有效, 设备上报就会注册成功, 不是第一次则会刷新心跳时间, 避免通信中断告警. 设备的报文结果以及设备的 CRC16 位置如下:
平台端需要重新对注册包内容 (不包含设备的 CRC 计算字节) 进行 CRC 校验计算, 与设备端的 CRC 校验对比. 如果相等, 则平台端的 CRC 校验成功.
备注: 本文的 CRC 校验全部指 CRC16 的校验.
2. CRC 校验的三种方法
本文侧重测试 CRC 的性能, 不讲 CRC 校验的原理, 因为 CRC 只是个校验数据准确性的工具, 而且每个报文(不单单心跳报文), 还有 AI,DI,DO,AO, 告警报文等都需要校验, 因此, 执行 CRC 程序段的性能显得尤为重要.
如果读者对 CRC 的校验原理感兴趣, 请自行网上搜索相关资料进行深入研究, 此处不再展开.
2.1. 直接计算 CRC 校验
以下代码已经做过验证, 与设备端的 CRC 校验码相等(协议是基于变种的私有 modbus 协议), 具体校验步骤可参考如下程序注释. 最终将此类封装在了 Crc16 的帮助类里面.
- /// <summary>
- /// 计算 CRC16 校验码
- /// </summary>
- /// <param name="value">校验数据</param>
- /// <param name="poly">多项式码</param>
- /// <param name="crcInit">校验码初始值</param>
- /// <returns></returns>
- public static byte[] GetCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
- {
- if (value == null || !value.Any())
- throw new ArgumentException("生成 CRC16 的入参有误");
- // 运算
- ushort crc = crcInit;
- for (int i = 0; i <value.Length-2; i++)
- {
- //Step1. 与校验对象的某字节取异或
- crc = (ushort)(crc ^ (value[i]));
- for (int j = 0; j < 8; j++)
- { //Step2.==0? 右移 1 比特, 否则右移 1 bit 与多项式异或
- crc = (crc & 1) != 0 ? (ushort)((crc>> 1) ^ poly) : (ushort)(crc>> 1);
- }
- }
- byte hi = (byte)((crc & 0xFF00)>> 8); // 高位置
- byte lo = (byte)(crc & 0x00FF); // 低位置
- //byte[] buffer = new byte[value.Length + 2];
- //value.CopyTo(buffer, 0);
- //buffer[buffer.Length - 1] = hi;
- //buffer[buffer.Length - 2] = lo;
- //return buffer;
- byte[] returnVal = new byte[2];
- returnVal[1] = hi;//CRC 高位
- returnVal[0] = lo;//CRC 低位
- return returnVal;
- }
2.2. 查短表法计算 CRC16 校验
查短表法计算 CRC16, 性能佳, 而且只需很小内存空间.
- static readonly UInt16[] crcTlb = new UInt16[16]{0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
- 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400};
- public static UInt16 CalcCRC16(byte[] pBuf)
- {
- byte i = 0, ch = 0;
- UInt16 crc = 0xFFFF;
- for (i = 0; i <pBuf.Length-2; i++)
- {
- ch = pBuf[i];
- crc = (UInt16)(crcTlb[(ch ^ crc) & 0x0F] ^ (crc>> 4));
- crc = (UInt16)(crcTlb[((ch>> 4) ^ crc) & 0x0F] ^ (crc>> 4));
- }
- crc = (UInt16)((crc & 0xFF) <<8 | (crc>> 8));
- return crc;
- }
2.3. 查大表法计算 CRC16 校验
校验结果调了 1 天没调成功, 后面会将测试结果贴出, 性能与查短表几乎一样, 而且浪费内存, 所以没有采用此法.
- static readonly UInt16[] CRC16Table =new UInt16[256] {
- 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
- 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
- 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
- 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
- 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
- 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
- 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
- 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
- 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
- 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
- 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
- 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
- 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
- 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
- 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
- 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
- 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
- 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
- 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
- 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
- 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
- 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
- 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
- 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
- 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
- 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
- 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
- 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
- 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
- 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
- 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
- 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
- };
- /// <summary>
- /// 查表法计算 CRC16.
- /// </summary>
- /// <param name="dataIn">待校验数据</param>
- /// <param name="length">数据长度</param>
- /// <returns > 校验值</returns>
- public static UInt16 calCRC16(byte[] dataIn, int length)
- {
- UInt16 i;
- UInt16 nAccum = 0;
- for (i = 0; i <length; i++)
- nAccum = (UInt16)((nAccum << 8) ^ (UInt16)CRC16Table[(nAccum>> 8) ^ dataIn[i]]);
- return nAccum;
- }
3. 三种校验方式的测试方法
3.1. 直接计算 CRC 校验的时间测试
- DateTime beforCrc = DateTime.Now;
- var CrcValue=CRC16.GetCRC16(validBuff);
- DateTime afterCrc = DateTime.Now;
- TimeSpan ts = afterCrc.Subtract(beforCrc);
- Console.WriteLine("校验结果{1}{2}. 直接计算 CRC 校验总 {0}ms.", ts.TotalMilliseconds,CrcValue[0].ToString("X , CrcValue[1].ToString("X2"));
3.2. 查短表计算 CRC 校验的时间测试
- beforCrc = DateTime.Now;
- var CrcValue_ShotTable=CRC16.CalcCRC16(validBuff);
- afterCrc = DateTime.Now;
- var ts_table = afterCrc.Subtract(beforCrc);
- Console.WriteLine("校验结果{1}. 查表计算 CRC 校验总共花费 ms.", ts_table.TotalMilliseconds, CrcValue_ShotTab ToString("X2"));
3.3. 查长表计算 CRC 校验的时间测试
根据网上的资源, 测试计算结果有问题. 校验结果昨天调了 1 天没调成功, 而且性能跟查短表几乎一样, 还需要占用更多内存, 所以直接 pass.
- beforCrc = DateTime.Now;
- var CrcValue_LongTable = CRC16.calCRC16(validBuf validBuff.Length-2);
- afterCrc = DateTime.Now;
- ts = afterCrc.Subtract(beforCrc);
- Console.WriteLine("校验结果{1}. 查长表计算 CRC 校验总 {0}ms.", ts.TotalMilliseconds, CrcValue_LongTab ToString("X2"));
4. 校验结果的测试
4.1. CRC 静态帮助类中的校验结果方法
这里最终是采用 2.2. 查短表法计算 CRC16 校验. 通过默认设置模式 mode="Table" 调用. 校验成功返回 true, 校验失败返回 false.
- /// <summary>
- /// 验证 CRC16 校验码
- /// </summary>
- /// <param name="value">校验数据(包含底端设备上传的 CRC 校验值)</param>
- /// <param name="poly">多项式码</param>
- /// <param name="crcInit">校验码初始值</param>
- /// <returns></returns>
- public static bool CheckCRC16(byte[] value, ushort poly = 0xA001, ushort crcInit = 0xFFFF,string mode="Table")
- {
- if (value == null || !value.Any())
- throw new ArgumentException("生成 CRC16 的入参有误");
- var crc16 = new byte[2];
- if (mode == "Table")
- {
- var result=CalcCRC16(value);
- crc16[0] = (byte)(result>> 8);
- crc16[1] = (byte)(result);
- }
- else
- {
- crc16 = GetCRC16(value, poly, crcInit);
- }
- if ((value[value.Length - 1] == crc16[crc16.Length - 1]) && (value[value.Length - 2] == crc16[crc16.Length - 2]))
- return true;
- return false;
- }
4.2. CRC 验证方法的顶层调用
测试 CRC 验证方法
- var result =CRC16.CheckCRC16(validBuff);
- Console.WriteLine("校验结果{0}.", result);
- LoggerHelper.Info("CRC 校验结果:" + BitConverter.ToStr(CrcValue));
5. 不同校验方式的性能差异
这里主要是对比 2.1 与 2.2. 方法 2.3 弃用.
- var diff = ts / ts_table;
- Console.WriteLine("直接计算所需时间是查表的的 {0} 倍", diff);
6. 结果输出
查长表计算 CRC 与查短表 CRC 校验计算, 性能基本一样, 甚至短表性能更佳;
查短表性能是直接计算的 3~5 倍, 所需计算时间是微秒级基本可以忽略不计;
查长表计算校验结果有问题, 4F0C 为不正确结果, 还需要占用更多内存, 所以直接 pass.
7. 小结
第一次输出性能时间需要比较久, 原因是 Net Core 刚启动完成, 需要做的事比较多.
来源: https://www.cnblogs.com/JerryMouseLi/p/12592565.html