以太网帧FCS校验:CRC32的三种实现

简介: 这篇文章讨论了CRC32校验码的三种实现方法,用于检测网络通信中的数据错误。首先介绍了基础的按位计算方法,虽然直观但效率低。接着,文章提出了使用非翻转查找表和翻转查找表的快速计算方法,后者在性能上更优。文中提供了C++代码示例,展示如何生成和使用这些查找表。最后,指出在实际应用中,应根据性能需求选择合适的方法。

改文章是我在2011年写于某sdn的, 搬运过来。


在网络通信中,循环冗余校验(CRC)是一种广泛使用的检错码,用于检测数据传输过程中的错误。其中,CRC32由于其良好的检错能力和相对较低的计算复杂度,成为许多协议(包括以太网)中首选的检错机制。本文将深入探讨CRC32校验码的三种实现方法,并提供详细的代码实现。


方法一:按位计算CRC32校验码

这是最基础也是最直观的实现方式,它逐位地进行异或和移位操作,适用于理解CRC算法原理,但计算效率较低。

代码实现:

Cpp

#include <cstdint>

uint32_t crc32_bitwise(uint32_t polynomial, uint32_t initial, const void *data, size_t len) {
    uint32_t crc = initial;
    const unsigned char *ptr = (const unsigned char *)data;
    
    for (size_t i = 0; i < len; i++) {
        crc ^= ((uint32_t)(*ptr++)) << 24;
        for (int j = 0; j < 8; j++) {
            if (crc & 0x80000000)
                crc = (crc << 1) ^ polynomial;
            else
                crc <<= 1;
        }
    }
    return crc;
}

方法二:使用非翻转的查找表进行快速计算

为了提高计算速度,可以预先生成一个查找表,每次处理一个字节的数据。然而,这种实现通常包含位翻转操作,影响了计算效率。

代码实现:

Cpp

#include <cstdint>
#include <array>

constexpr uint32_t POLYNOMIAL = 0xEDB88320;
std::array<uint32_t, 256> crc_table;

void generate_crc_table() {
    for (int i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++) {
            if (crc & 1)
                crc = (crc >> 1) ^ POLYNOMIAL;
            else
                crc >>= 1;
        }
        crc_table[i] = crc;
    }
}

uint32_t crc32_lookup(uint32_t initial, const void *data, size_t len) {
    uint32_t crc = ~initial;
    const unsigned char *ptr = (const unsigned char *)data;
    
    for (size_t i = 0; i < len; i++)
        crc = (crc >> 8) ^ crc_table[(crc ^ *ptr++) & 0xFF];
    
    return ~crc;
}

方法三:使用翻转的查找表进行快速计算

这种方法通过预先翻转输入数据和查找表中的值,避免了在计算过程中的位翻转操作,从而极大地提高了计算速度。

代码实现:

Cpp

#include <cstdint>
#include <array>

constexpr uint32_t POLYNOMIAL = 0xEDB88320;
std::array<uint32_t, 256> crc_table;

void generate_crc_table_flip() {
    for (int i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++) {
            crc = (crc >> 1) ^ (-(crc & 1) & POLYNOMIAL);
        }
        crc_table[i] = crc;
    }
}

uint32_t crc32_lookup_flip(uint32_t initial, const void *data, size_t len) {
    uint32_t crc = initial;
    const unsigned char *ptr = (const unsigned char *)data;
    
    for (size_t i = 0; i < len; i++)
        crc = crc_table[(crc ^ *ptr++) & 0xFF] ^ (crc >> 8);
    
    return crc;
}

结论

在实际应用中,根据性能需求和资源限制选择合适的CRC32实现至关重要。尽管按位计算提供了最清晰的算法理解,但在性能要求较高的场景下,使用查找表的方法(特别是翻转的查找表)能提供显著的速度优势。希望本文的代码实现能帮助读者更好地理解和应用CRC32校验码。




完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
 
 
 
 
#define alt_8    char
#define alt_u8   unsigned char
#define alt_32   int
#define alt_u32  unsigned int
#define alt_64   long long
#define alt_u64  unsigned long long
 
 
//位翻转函数
alt_u64 Reflect(alt_u64 ref,alt_u8 ch)
{   
    int i;
    alt_u64 value = 0;
    for( i = 1; i < ( ch + 1 ); i++ )
    {
        if( ref & 1 )
            value |= 1 << ( ch - i );
        ref >>= 1;
    }
    return value;
}
 
 
//标准的CRC32多项式
#define poly  0x04C11DB7
//翻转的CRC32多项式
#define upoly 0xEDB88320
 
 
 
alt_u32 crc32_bit(alt_u8 *ptr, alt_u32 len, alt_u32 gx)
{
    alt_u8 i;
    alt_u32 crc = 0xffffffff;
    while( len-- )
    {
        for( i = 1; i != 0; i <<= 1 )
        {
            if( ( crc & 0x80000000 ) != 0 )
            {
                crc <<= 1;
                crc ^= gx;
            }
            else 
                crc <<= 1;
            if( ( *ptr & i ) != 0 ) 
                crc ^= gx;
        }
        ptr++;
    }
    return ( Reflect(crc,32) ^ 0xffffffff );
}
 
 
 
 
 
 
alt_u32 Table1[256];
alt_u32 Table2[256];
 
 
 
 
 // 生成CRC32 普通表 , 第二项是04C11DB7
void gen_direct_table(alt_u32 *table)
{
    alt_u32 gx = 0x04c11db7;
    unsigned long i32, j32;
    unsigned long nData32;
    unsigned long nAccum32;
    for ( i32 = 0; i32 < 256; i32++ )
    {
        nData32 = ( unsigned long )( i32 << 24 );
        nAccum32 = 0;
        for ( j32 = 0; j32 < 8; j32++ )
        {
            if ( ( nData32 ^ nAccum32 ) & 0x80000000 )
                nAccum32 = ( nAccum32 << 1 ) ^ gx;
            else
                nAccum32 <<= 1;
            nData32 <<= 1;
        }
        table[i32] = nAccum32;
    }
}
 
 
// 生成CRC32 翻转表 第二项是77073096
void gen_normal_table(alt_u32 *table)
{
    alt_u32 gx = 0x04c11db7;
    alt_u32 temp,crc;
    for(int i = 0; i <= 0xFF; i++) 
    {
        temp=Reflect(i, 8);
        table[i]= temp<< 24;
        for (int j = 0; j < 8; j++)
        {
            unsigned long int t1,t2;
            unsigned long int flag=table[i]&0x80000000;
            t1=(table[i] << 1);
            if(flag==0)
            t2=0;
            else
            t2=gx;
            table[i] =t1^t2 ;
        }
        crc=table[i];
        table[i] = Reflect(table[i], 32);
    }
}
 
 
 
alt_u32 DIRECT_TABLE_CRC(alt_u8 *ptr,int len, alt_u32 * table) 
{
    alt_u32 crc = 0xffffffff; 
    alt_u8 *p= ptr;
    int i;
    for ( i = 0; i < len; i++ )
        crc = ( crc << 8 ) ^ table[( crc >> 24 ) ^ (alt_u8)Reflect((*(p+i)), 8)];
    return ~(alt_u32)Reflect(crc, 32) ;
}
 
 
 
 
alt_u32 Reverse_Table_CRC(alt_u8 *data, alt_32 len, alt_u32 * table)
{
    alt_u32 crc = 0xffffffff;  
    alt_u8 *p = data;
    int i;
    for(i=0; i <len; i++)
        crc =  table[( crc ^( *(p+i)) ) & 0xff] ^ (crc >> 8);
    return  ~crc ; 
}
 
 
 
//这是一个完整的以太网帧。最后四个字节 8b 6b f5 13是其FCS字段,用于与后面生成的CRC32对照
alt_u8  tx_data[] = {
        0xff,   0xff,   0xff,   0xff,   0xff,   0xff,   0x00,   0x1f,   //8
        0x29,   0x00,   0xb5,   0xfa,   0x08,   0x06,   0x00,   0x01,   //15
        0x08,   0x00,   0x06,   0x04,   0x00,   0x01,   0x00,   0x1f,   //24
        0x29,   0x00,   0xb5,   0xfa,   0xac,   0x15,   0x0e,   0xd9,   //32
        0x00,   0x00,   0x00,   0x00,   0x00,   0x00,   0xac,   0x15,   //40
        0x0e,   0x8e,   0x00,   0x00,   0x00,   0x00,   0x00,   0x00,   //48
        0x00,   0x00 ,  0x00,   0x00,   0x00,   0x00,   0x00,   0x00,   //56
        0x00,   0x00,   0x00,   0x00,   0x8b,   0x6b,   0xf5,   0x13    //64
};
 
 
 
 
int main()
{
    alt_u8 *data = tx_data;
    alt_u32 dataLen = sizeof(tx_data) -4;
  
    int sum = 256;
    int i = 0;
 
    //生成普通表,用于直接计算的表
    gen_direct_table(Table1);
    printf("Table1 :\n");
    for( i = 0; i < sum; i++)
    {
        if(i<16)
            printf("%08x ",Table1[i]);
    }
    printf("\n\n");
 
 
    //生成翻转表,是官方推荐的,故称其为normal_table
    gen_normal_table(Table2);
    printf("Table2 :\n");
    for( i = 0; i < sum; i++)
    {
        if(i<16)
            printf("%08x ",Table2[i]);
    }
    printf("\n\n");
 
 
 
    printf("dataLen = %d\n",dataLen);//打印数据长度,应该是60字节。
 
 
 
    //计算并打印出CRC32校验码,应该是0x13f56b8b
 
    //按照bit进行校验,最慢
    printf("Slow CRC by bit          : %08x\n",crc32_bit( data, dataLen, 0x04c11db7 ));
    //使用普通表,非官方,很慢
    printf("Direct Table  ref + xor  : %08x\n",DIRECT_TABLE_CRC (data,dataLen,Table1)); 
    //使用翻转表,官方推荐的,很快
    printf("Reverse Table  ref + xor : %08x\n",Reverse_Table_CRC(data,dataLen,Table2)); 
 
 
    system("pause");    
    return 0;
}
相关文章
|
6月前
|
算法
计算机网络——数据链路层-差错检测(奇偶校验、循环冗余校验CRC)
计算机网络——数据链路层-差错检测(奇偶校验、循环冗余校验CRC)
368 0
|
6月前
计算机网络——数据链路层-封装成帧(帧定界、透明传输-字节填充,比特填充、MTU)
计算机网络——数据链路层-封装成帧(帧定界、透明传输-字节填充,比特填充、MTU)
346 0
|
7月前
CRC与FCS的区别和联系
CRC与FCS的区别和联系
283 0
|
自然语言处理 网络协议 算法
UDP协议和报文格式,校验和,CRC的含义
UDP协议和报文格式,校验和,CRC的含义
CRC校验-基于MODBUS协议实现源码
CRC校验-基于MODBUS协议实现源码
116 0
|
7月前
|
传感器 C++
计算机网络:数据链路层之差错控制、奇偶校验码、CRC循环冗余码、海明码
计算机网络:数据链路层之差错控制、奇偶校验码、CRC循环冗余码、海明码
170 0
|
7月前
|
算法
【数据链路层】循环冗余码CRC、后退N帧协议GBN、选择重传协议SR、CSMA/CA
【数据链路层】循环冗余码CRC、后退N帧协议GBN、选择重传协议SR、CSMA/CA
112 0
详解CAN总线:CAN总线报文格式—错误帧
CAN总线上传输的信息称为报文,当总线空闲时任何连接的单元都可以开始发送新的报文。
|
存储 网络协议 网络安全
计算机网络,CRC码计算生成
网络协议概念:网络协议是计算机网络相互通信的对等层实体之间交换信息时必须遵守的规则或约定的集合。[网络协议是网络上所有设备(网络服务器、计算机及交换机、路由器、防火墙等)之间通信规则的集合,它规定了通信时信息必须采用的格式。TCP/IP协议、IPX/SPX协议。网络协议的三个基本要素:语法、语义和同步;语法:数据和控制信息的结构或格式②语义:用于协调和进行差错处理的控制信息③时序(同步):是对事件实现顺序的详细说明
208 1
计算机网络,CRC码计算生成
|
传感器 人机交互
STM32:串口收发HEX数据包理论篇(内含:1.实验现象+2.文本数据包/HEX数据包+ 3.文本数据包接收/HEX数据包接收)
STM32:串口收发HEX数据包理论篇(内含:1.实验现象+2.文本数据包/HEX数据包+ 3.文本数据包接收/HEX数据包接收)
660 0
STM32:串口收发HEX数据包理论篇(内含:1.实验现象+2.文本数据包/HEX数据包+ 3.文本数据包接收/HEX数据包接收)