標籤:
在物聯網平台設計過程中,我的中介軟體一方面需要處理來自於硬體端的包,另一方面需要處理來自於使用者端的包,使用者端包括web端和手機端等等。所以編寫一個統一的CRC認證是非常必須要。
那麼,在設計開始,CRC認證到底是什麼呢?所謂的CRC認證,就是指,在硬體端或者使用者端進行資料轉送前,通過一套演算法,將待傳輸的資料,通過加驗,算出其校正碼,附加在包體的最後,然後中介軟體收到此包後,對包進行解析,拿出其中的資料內容部分,然後對包重新進行一次CRC加驗,如果本次加驗結果和包體附帶的CRC校正碼資料一致,那麼就說明此條資料包是完整的,可用的。反之則證明此包有問題。
所以從上面過程中,我們可以看出CRC包含這麼幾個重要的內容:CRC產生演算法,CRC解析演算法。
CRC查表演算法
首先,CRC產生演算法如下,從網上隨便都能搜出一堆,我們這裡用的是CRC16位的:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DSMiddlewire.Lib{ public class CRC16 { private const int CRC_LEN = 0; // Table of CRC values for high-order byte private readonly byte[] _auchCRCHi = new byte[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 }; // Table of CRC values for low-order byte private readonly byte[] _auchCRCLo = new byte[] { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 }; /// <summary> /// 獲得CRC16效驗碼 /// </summary> /// <param name="buffer"></param> /// <returns></returns> internal ushort CalculateCrc16(byte[] buffer) { byte crcHi = 0xff; // high crc byte initialized byte crcLo = 0xff; // low crc byte initialized for (int i = 0; i < buffer.Length - CRC_LEN; i++) { int crcIndex = crcHi ^ buffer[i]; // calculate the crc lookup index crcHi = (byte)(crcLo ^ _auchCRCHi[crcIndex]); crcLo = _auchCRCLo[crcIndex]; } return (ushort)(crcHi << 8 | crcLo); } /// <summary> /// 獲得CRC16效驗碼 /// </summary> /// <param name="strPar"></param> /// <returns></returns> public static string CalculateCrc16(string strPar) { string retStr = new CRC16().CalculateCrc16(System.Text.Encoding.Default.GetBytes(strPar)).ToString(); while (retStr.Length < 5) { retStr = "0" + retStr; } return retStr; } }}
上面就是利用查表法來產生CRC16校正碼的函數,其機理就是:我通過運算會得到一個CRC校正碼,然後除以256會得到高位值,餘256會得到低位值,最後將高位值和低位值進行或操作,就是我們的校正碼了。
CRC加驗方法
上面的函數雖然可用,但是產生的卻是String類型的值,在資料轉送過程中,都是要轉化為byte傳輸的,所以這裡我們需要將得到的數值轉化為byte類型,然後放到資料內容的byte數組中,傳送給中介軟體:
/* * 向底層硬體發送的CRC封裝方法 * 1.var crcGenerate = GetCRC(result); 先得到crc碼:14321 * 2.得到高位,低位值: 14321/256 = 55, 14321%256=241 * 3.轉為byte,追加到最後兩位byte數組中,傳給硬體即可 * */ public static byte[] ConstructMessageWithCRC(string message) { //資訊主體,加上一個分隔字元 var messageToConstruct = message + "|"; //算出crc碼 var crcCode = CRC16.CalculateCrc16(messageToConstruct); var crcCodeShort = ushort.Parse(crcCode); //CRC碼高位 var crcHigh = byte.Parse((crcCodeShort / 256).ToString()); //CRC碼低位 var crcLow = byte.Parse((crcCodeShort % 256).ToString()); var messageBytes = Encoding.Default.GetBytes(messageToConstruct); var messageLength = messageBytes.Length; var messageBytesWithCRC = new byte[messageLength + 2]; Array.Copy(messageBytes, 0, messageBytesWithCRC, 0, messageLength); //放CRC碼到數組最後兩位 messageBytesWithCRC[messageLength] = crcHigh; messageBytesWithCRC[messageLength + 1] = crcLow; return messageBytesWithCRC; }
我解釋下上面的代碼:messageToConstruct 是我們的資料內容部分,我們先是將資料內容通過CRC16.CalculateCrc16函數算出其校正碼,然後將其最高位和最低位分別放入byte數組中,然後傳送出去。
CRC校正方法
那麼既然有CRC校正碼的產生,也需要CRC校正碼的解析過程,所謂有攻必有防:
public static bool CheckMessageCRC(byte[] message, out string messageReceived) { //接收到的資料長度 var messageLength = message.Length; //收到的資料資訊 var messageReceivedStrBytes = new byte[messageLength - 2]; Array.Copy(message, 0, messageReceivedStrBytes, 0, messageLength - 2); //CRC校正碼固定2個位元組 var messageReceivedCrcBytes = new byte[2]; Array.Copy(message, messageLength - 2, messageReceivedCrcBytes, 0, 2); //擷取傳輸資料 var messageCalculatedString = Encoding.Default.GetString(messageReceivedStrBytes); messageReceived = messageCalculatedString; //擷取CRC校正碼 var currentCRC = byte.Parse(messageReceivedCrcBytes[0].ToString()) * 256 + byte.Parse(messageReceivedCrcBytes[1].ToString()); //資料部分的crc校正 var result = ushort.Parse(CRC16.CalculateCrc16(messageCalculatedString)); if (currentCRC == result) return true; return false; }
上面的過程就是對接收到的資料包,先把資料部分和CRC校正碼部分分開(校正碼部分很明確占最後兩個位元組,從CRC加驗部分我們就可以看到)。然後再把資料部分重新計算CRC校正碼,和附帶的對比即可。
後話
好了,以上就是所有的CRC校正碼內容了,包括加驗和校正過程,你明白了嗎?
說實在話,翻了許多文章,都沒有我這篇講得透徹。因為我在設計之初,也是找網上的文章,但是均不成功,後來和做硬體的人溝通,然後順利成型。
物聯網平台設計心得:你所不知道的CRC校正