今天简单介绍一些传输数据校验的方法,就昨天整理的资料和就我的理解写的Demo做个总结!希望大家多多指教!
定义
通俗的说,就是为保证数据的完整性,用一种指定的算法对原始数据计算出的一个校验值。接收方用同样的算法计算一次校验值,如果和随数据提供的校验值一样,说明数据是完整的。
实际应用
校验方法
数据校验无非就是三个步骤:一、添加校验码;二、校验数据;三、还原数据。 如下图:(具体方法后面一一介绍)
先看看这次咱们学习哪些,我在里面定义的一个枚举
public enum VerifyType { /// <summary> /// 无校验 /// </summary> None, /// <summary> /// 奇校验 /// </summary> Odd, /// <summary> /// 偶校验 /// </summary> Even, /// <summary> /// 1校验 /// </summary> Mark, /// <summary> /// 0校验 /// </summary> Space, /// <summary> /// 循环冗余码CRC检验 /// </summary> CRC, /// <summary> /// 异或校验 /// </summary> BCC, /// <summary> /// 和校验 /// </summary> Sum, /// <summary> /// MD5 /// </summary> MD5 }
然后就是对应这三个方法的测试调用了:
static void Main(string[] args) { //校验方式选择 Verify.VerifyType T = Verify.VerifyType.None; Console.WriteLine("验证方式{0} : ================================================================== ", T); byte[] sendDate = { 10, 252, 253, 254, 255, 50, 51, 66, 85, 11 }; Console.WriteLine("待发送数据:"); for (int i = 0; i < sendDate.Length; i++) { Console.Write(sendDate[i] + " "); } Console.WriteLine(); //添加校验码 sendDate = Verify.AddCode(sendDate, T); Console.WriteLine("增加校验码的发送数据:"); for (int i = 0; i < sendDate.Length; i++) { Console.Write(sendDate[i] + " "); } Console.WriteLine(" ………………………………………………………………………………………………………… "); Console.WriteLine("接收数据校验:------------"); if (Verify.CheckCode(sendDate, T)) { Console.WriteLine("校验成功"); sendDate = Verify.RestoringData(sendDate, T); } else { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("校验失败!!!!"); } for (int i = 0; i < sendDate.Length; i++) { Console.Write(sendDate[i] + " "); } Console.ReadKey(); }
串口通讯过程中有五种校验方式,分别是无校验(None),奇校验(Odd),偶校验(Even),1校验(Mark),0校验(Space)。
无校验(None)就是直接发送数据没有校验位,所以没什么好介绍的,直接看看运行结果吧!
奇偶校验Parity Check (下面介绍来自百度百科)
简单例子:
奇校验(Odd),这里是应用在软件传输上的校验而不是硬件编程而且是例子所以写的比较简单易懂
算法:
1 /// <summary> 2 /// 奇校验算法获得校验码 3 /// </summary> 4 /// <param name="date"></param> 5 /// <returns></returns> 6 private static byte[] OddCode(byte[] data) 7 { 8 byte[] bVerify = new byte[data.Length / 8 + 2];//校验位接收数组 9 bVerify[bVerify.Length - 1] = (byte)bVerify.Length;//校验位长度 10 //计算校验位数据 11 for (int i = 0; i < data.Length; i++)//每个位去处理 12 { 13 if (GetOddEvenVerify(data[i])) 14 {//奇数 //0; 15 clr_bit(ref bVerify[i / 8], i); 16 } 17 else 18 {//偶数//1; 19 set_bit(ref bVerify[i / 8], i); 20 } 21 } 22 return bVerify; 23 } 24 /// <summary> 25 /// 判断当前位1的个数是奇数个还是偶数个 26 /// </summary> 27 /// <param name="bData"></param> 28 /// <returns>True 为奇数个 False 为偶数个</returns> 29 private static bool GetOddEvenVerify(byte bData) 30 { 31 byte bcCount = 0; /* 字节内1的个数 */ 32 33 for (int i = 0; i < 8; i++) 34 { 35 if ((bData & (byte)0x01) == 1) 36 { 37 bcCount++; 38 } 39 bData >>= 1; 40 } 41 42 return ((bcCount & (byte)0x01) == 1); 43 } 44 /// <summary> 45 /// 置位x的y位 46 /// </summary> 47 /// <param name="x"></param> 48 /// <param name="y"></param> 49 private static void set_bit(ref byte x, int y) 50 { 51 x = (byte)(x | (0x01 << (y))); 52 53 } 54 /// <summary> 55 /// 清零x的y位 56 /// </summary> 57 /// <param name="x"></param> 58 /// <param name="y"></param> 59 private static void clr_bit(ref byte x, int y) 60 { 61 x = (byte)(x & ~(0x01 << (y))); 62 }
无论是奇校验还是偶校验都是需要统计校验位数据中1的个数GetOddEvenVerify,还有就是对校验码相应位进行标记为1还是0的方法 set_bit 和 clr_bit 这三个公用的方法,然后就是对原始数据进行计算得到校验码并附到原始数据后面(当然你也可以放到前面)。
在这我就用了1byte 作为标记校验位的长度所以能标记的范围是很有限的只有255位
添加校验 :
case VerifyType.Odd: if ((data.Length / 8 + 1) > 255) return newDate;//校验位长度大于255就超出这次比较算法处理范围---收敛 byte[] bVerifyOdd = OddCode(data); newDate = new byte[data.Length + bVerifyOdd.Length]; data.CopyTo(newDate, 0); bVerifyOdd.CopyTo(newDate, data.Length); break;
校验数据 :
case VerifyType.Odd: int lenOdd = data[data.Length - 1]; byte[] sourceBVerifyOdd = new byte[lenOdd]; sourceBVerifyOdd = data.Skip(data.Length - lenOdd).Take(lenOdd).ToArray(); byte[] nowBVerifyOdd = OddCode(RestoringData(data, t)); return sourceBVerifyOdd.SequenceEqual(nowBVerifyOdd);
还原数据 :(奇校验 偶校验数据还原都是一样的算法)
case VerifyType.Odd: case VerifyType.Even: return data.Take(data.Length - (data[data.Length - 1])).ToArray();
运行结果:
偶校验(Even)
算法:(与奇校验算法唯一不同的就是补码位的0和1正好相反)
/// <summary> /// 偶校验算法获得校验码 /// </summary> /// <param name="date"></param> /// <returns></returns> private static byte[] EvenCode(byte[] data) { byte[] bVerify = new byte[data.Length / 8 + 2];//校验位接收数组 bVerify[bVerify.Length - 1] = (byte)bVerify.Length;//校验位长度 //计算校验位数据 for (int i = 0; i < data.Length; i++)//每个位去处理 { if (GetOddEvenVerify(data[i])) {//奇数 //1; set_bit(ref bVerify[i / 8], i); } else {//偶数//0; clr_bit(ref bVerify[i / 8], i); } } return bVerify; }
添加校验码:
case VerifyType.Even: if ((data.Length / 8 + 1) > 255) return newDate;//校验位长度大于255就超出这次比较算法处理范围---收敛 byte[] bVerifyEven = EvenCode(data); newDate = new byte[data.Length + bVerifyEven.Length]; data.CopyTo(newDate, 0); bVerifyEven.CopyTo(newDate, data.Length); break;
检验校验码:
case VerifyType.Even: int lenEven = data[data.Length - 1]; byte[] sourceBVerifyEven = new byte[lenEven]; sourceBVerifyEven = data.Skip(data.Length - lenEven).Take(lenEven).ToArray(); byte[] nowBVerifyEven = EvenCode(RestoringData(data, t)); return sourceBVerifyEven.SequenceEqual(nowBVerifyEven);
运行结果:
1校验(Mark),0校验(Space)
校验方式设置为1校验(Mark),校验位固定为1;如果校验方式设置为0校验(Space),校验位固定为0;
添加校验码:
case VerifyType.Mark: newDate = new byte[data.Length + 1]; data.CopyTo(newDate, 0); newDate[data.Length] = 1; break; case VerifyType.Space: newDate = new byte[data.Length + 1]; data.CopyTo(newDate, 0); newDate[data.Length] = 0; break;
检验校验码:
case VerifyType.Mark: return data[data.Length - 1] == 1; case VerifyType.Space: return data[data.Length - 1] == 0;
数据还原:
case VerifyType.Mark: case VerifyType.Space: case VerifyType.BCC: return data.Take(data.Length - 1).ToArray();
运行结果:
bcc异或校验法(block check character)
/// <summary> /// 异或校验 校验码 /// </summary> /// <param name="data"></param> /// <returns></returns> private static byte ParityCode(byte[] data) { byte CheckCode = 1; //异或校验 for (int i = 0; i < data.Length; i++) { CheckCode ^= data[i]; } return CheckCode; }
添加校验码:
case VerifyType.BCC: newDate = new byte[data.Length + 1]; data.CopyTo(newDate, 0); newDate[data.Length] = ParityCode(data); break;
数据校验:
case VerifyType.BCC: return (ParityCode(RestoringData(data, t)) == data[data.Length - 1]);
数据还原与之前 1校验和0校验一样,截去最后一位
和校验
这里拿一个int值作为保存结果所以是32位的4byte的校验码
算法:
/// <summary> /// 累加和 /// </summary> /// <param name="data"></param> /// <returns></returns> private static byte[] SumCode(byte[] data) { int sum = 0; foreach (var item in data) { sum += item; } byte[] code = intToBytes(sum); return code; }
添加校验码:
case VerifyType.Sum: newDate = new byte[data.Length + 4]; data.CopyTo(newDate, 0); SumCode(data).CopyTo(newDate, data.Length); break;
数据校验:
case VerifyType.Sum: //方法一: //int sourceSum = byteToInt(data.Skip(data.Length - 4).ToArray()); //int newSum = byteToInt(SumCode(RestoringData(data, t))); //return sourceSum.Equals(newSum); //方法二: return data.Skip(data.Length - 4).ToArray().SequenceEqual(SumCode(RestoringData(data, t)));
还原数据:
case VerifyType.Sum: return data.Take(data.Length - 4).ToArray();
运行结果:
MD5校验和数字签名
/// <summary> /// 计算data字节数组的哈希值 /// </summary> /// <param name="data"></param> /// <returns></returns> private static byte[] MD5Code(byte[] data) { System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); return md5.ComputeHash(data);//计算data字节数组的哈希值 }
添加校验码(数据签名):
case VerifyType.MD5: byte[] md5data = MD5Code(data);//计算data字节数组的哈希值 newDate = new byte[data.Length + md5data.Length]; data.CopyTo(newDate, 0); md5data.CopyTo(newDate, data.Length); break;
数据校验:
case VerifyType.MD5: byte[] sourceMD5 = data.Skip(data.Length - 16).ToArray(); byte[] newMD5 = MD5Code(RestoringData(data, t));//计算data字节数组的哈希值 return sourceMD5.SequenceEqual(newMD5);
数据还原:
case VerifyType.MD5: return data.Take(data.Length - 16).ToArray();
运行结果: