在通信和数据传输领域,数据完整性检验是一个必不可少的环节。CRC16 作为一种广泛应用的校验算法,以其高效可靠而受到开发者青睐。本文将从基础到进阶,详细讲解如何在 C# 中实现 CRC16 算法,帮助你掌握这一重要的数据校验技术。
CRC(循环冗余校验,Cyclic Redundancy Check)是一种错误检测码,用于验证数据在传输或存储过程中是否出现错误。CRC16 是其中使用 16 位宽度的一种实现,广泛应用于:
下面是一个标准的 CRC16 实现,使用了查表法来提高计算效率:
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppCRC16
{
public class CRC16
{
// 标准 CRC16 多项式
private const ushort POLYNOMIAL = 0xA001;
// 预计算的查找表
private readonly ushort[] table = new ushort[256];
public CRC16()
{
// 生成 CRC16 查找表
for (ushort i = 0; i < table.Length; ++i)
{
ushort value = 0;
ushort temp = i;
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x0001) != 0)
{
value = (ushort)((value >> 1) ^ POLYNOMIAL);
}
else
{
value >>= 1;
}
temp >>= 1;
}
table[i] = value;
}
}
public ushort ComputeChecksum(byte[] bytes)
{
ushort crc = 0; // 初始值
for (int i = 0; i < bytes.Length; ++i)
{
byte index = (byte)(crc ^ bytes[i]);
crc = (ushort)((crc >> 8) ^ table[index]);
}
return crc;
}
}
}
C#using System.Text;
namespace AppCRC16
{
internal class Program
{
static void Main(string[] args)
{
CRC16 crc16 = new CRC16();
// 示例1:计算字符串的 CRC16
string text = "Hello, World!";
byte[] bytes = Encoding.ASCII.GetBytes(text);
ushort checksum = crc16.ComputeChecksum(bytes);
Console.WriteLine($"CRC16 of '{text}': 0x{checksum:X4}");
// 示例2:计算字节数组的 CRC16
byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
checksum = crc16.ComputeChecksum(data);
Console.WriteLine($"CRC16 of byte array: 0x{checksum:X4}");
Console.ReadLine();
}
}
}
在实际应用中,不同的协议和系统可能使用不同的 CRC16 变体。下面是一个支持多种 CRC16 标准的实现:
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppCRC16
{
internal class CRC16Calculator
{
public enum CRC16Mode
{
Standard, // 标准 CRC16
CCITT, // CRC16-CCITT
Modbus // Modbus-CRC16
}
private readonly ushort[] table;
private readonly ushort polynomial;
private readonly ushort initialValue;
public CRC16Calculator(CRC16Mode mode = CRC16Mode.Standard)
{
switch (mode)
{
case CRC16Mode.CCITT:
polynomial = 0x1021;
initialValue = 0xFFFF;
break;
case CRC16Mode.Modbus:
polynomial = 0xA001;
initialValue = 0xFFFF;
break;
default: // Standard
polynomial = 0xA001;
initialValue = 0x0000;
break;
}
table = new ushort[256];
GenerateTable();
}
private void GenerateTable()
{
for (int i = 0; i < 256; i++)
{
ushort value = 0;
ushort temp = (ushort)(i << 8);
for (int j = 0; j < 8; j++)
{
if (((value ^ temp) & 0x8000) != 0)
{
value = (ushort)((value << 1) ^ polynomial);
}
else
{
value <<= 1;
}
temp <<= 1;
}
table[i] = value;
}
}
public ushort Calculate(byte[] data)
{
ushort crc = initialValue;
for (int i = 0; i < data.Length; i++)
{
byte index = (byte)((crc >> 8) ^ data[i]);
crc = (ushort)((crc << 8) ^ table[index]);
}
return crc;
}
}
}
C#using System.Text;
namespace AppCRC16
{
internal class Program
{
static void Main(string[] args)
{
byte[] testData = Encoding.ASCII.GetBytes("Test Data");
// 使用标准 CRC16
var standardCrc = new CRC16Calculator(CRC16Calculator.CRC16Mode.Standard);
ushort standardResult = standardCrc.Calculate(testData);
Console.WriteLine($"Standard CRC16: 0x{standardResult:X4}");
// 使用 CCITT 变体
var ccittCrc = new CRC16Calculator(CRC16Calculator.CRC16Mode.CCITT);
ushort ccittResult = ccittCrc.Calculate(testData);
Console.WriteLine($"CCITT CRC16: 0x{ccittResult:X4}");
// 使用 Modbus 变体
var modbusCrc = new CRC16Calculator(CRC16Calculator.CRC16Mode.Modbus);
ushort modbusResult = modbusCrc.Calculate(testData);
Console.WriteLine($"Modbus CRC16: 0x{modbusResult:X4}");
Console.ReadLine();
}
}
}
在处理二进制数据时,经常需要在十六进制字符串和字节数组之间进行转换:
C#using System.Text;
namespace AppCRC16
{
public static class CRC16Utilities
{
public static string ToHexString(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "");
}
public static byte[] HexStringToBytes(string hex)
{
if (string.IsNullOrEmpty(hex))
return new byte[0];
if (hex.Length % 2 == 1)
hex = "0" + hex;
byte[] result = new byte[hex.Length / 2];
for (int i = 0; i < result.Length; i++)
{
result[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return result;
}
}
internal class Program
{
static void Main(string[] args)
{
// 示例数据
string hexString = "48656C6C6F"; // "Hello" 的十六进制表示
// 将十六进制字符串转换为字节数组
byte[] data = CRC16Utilities.HexStringToBytes(hexString);
// 计算 CRC16
var calculator = new CRC16Calculator(CRC16Calculator.CRC16Mode.Modbus);
ushort crc = calculator.Calculate(data);
// 输出结果
Console.WriteLine($"输入 (十六进制): {hexString}");
Console.WriteLine($"CRC16 校验值: 0x{crc:X4}");
// 验证数据完整性
byte[] dataWithCrc = new byte[data.Length + 2];
Array.Copy(data, dataWithCrc, data.Length);
dataWithCrc[data.Length] = (byte)(crc & 0xFF); // 低字节
dataWithCrc[data.Length + 1] = (byte)(crc >> 8); // 高字节
Console.WriteLine($"带校验和的完整消息: {CRC16Utilities.ToHexString(dataWithCrc)}");
Console.ReadLine();
}
}
}
不同的应用场景需要不同的 CRC16 实现,主要差异在于:
在实际应用中,需要确保使用的是正确的 CRC16 变体,否则会导致校验失败。
在某些协议中,CRC16 的高字节和低字节的顺序可能不同:
[CRC_L, CRC_H]
[CRC_H, CRC_L]
使用时需要注意按照协议规定的顺序处理字节。
确保输入数据的格式正确,特别是在处理十六进制字符串时。常见错误包括:
本文详细介绍了 C# 中 CRC16 算法的实现方法,从基础的标准实现到支持多种变体的进阶实现,并提供了性能优化建议和实用工具方法。通过这些代码示例和说明,你应该能够在自己的项目中正确实现和使用 CRC16 算法,确保数据传输的完整性和可靠性。
无论是在工业通信、嵌入式系统还是网络应用中,CRC16 都是一种重要的数据校验技术。掌握它的实现原理和使用方法,将帮助你构建更加可靠的软件系统。
如果你在实际应用中遇到任何问题,欢迎在评论区留言讨论!
关键词:C#, CRC16, 循环冗余校验, 数据校验, Modbus, CCITT, 数据完整性, 工业通信, 校验算法, .NET编程
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!