1. 程式人生 > >[技術棧]CRC校驗原理及C#程式碼實現CRC16、CRC32計算FCS校驗碼

[技術棧]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校驗碼

MH的多項式除法運算,可以用模2除法運算計算。下面為以生成多項式為H10010110的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) 演算法原理

CRC查詢表法推導及程式碼實現比較

CRC(迴圈冗餘校驗)線上計