基于Java的CRC冗余校验个人心得及实现---以CRC16_MODBUS协议为例

一、相关背景
1、CRC校验码的基本思想是利用线性编码理论,在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的监督码(既CRC码)r位,并附在信息后边,构成一个新的二进制码序列数共(k+ r)位,最后发送出去。在接收端,则根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。
2、CRC的本质是模-2除法的余数,采用的除数不同,CRC的类型也就不一样。 实际代码使用中,模-2除法与 ^(异或) 效果相同,个人喜欢用无进位加法理解。
3、CRC校验有严格的校验方式,实际报文解析中,可能存在字节按位逆序的情况,但有些报文中经常不明确指出,下文中会使用一种我常用的方式。

4、本文是我查看网上的很多博客后的个人总结与理解,如果有错误或者代码处理不够优化的地方,请及时指出,及时更新。

二、CRC算法基本规则(正向)
1、根据标准选择初值(生成寄存器)。

2、将数据的第一个字节与寄存器高8位异或。

3、判断最高位,若该位为 0 左移一位,若为 1 左移一位再与多项式Hex码异或。

4、重复3操作直至8位全部移位计算结束。

5、重复将所有输入数据操作完成以上步骤,所得16位数即16CRC校验码。

6、补充,这里的标准要格外注意,标准中会规定高地位以及是否按位反转。
三、CRC16_MODBUS协议规则

1、此处附加一个自己经常测试校验结果的在线网站,里面有各种协议标准的参数模型和在线校验:
http://www.ip33.com/crc.html
2、CRC16_MODBUS基本规则:
①字节高位在左,地位在右
②寄存器初始值为0xFFFF
③待测数据的每个字节操作前需要按位反转
④计算结果按位反转后与0x0000异或
⑤寄存器宽度为16 

四、CRC16_MODBUS正向校验代码

//正序校验    
    public static String Crc16Left(String dataHexStr)
    {
        //字符串类型的16进制字符串转换为字节数组
        byte[] data = CrcCheckTool.hex2Bytes(dataHexStr);
        //初始化寄存器 这个转为short类型 是因为java中 short类占16个字节
        short crci= (short) 0xffff;
        int i,j;
        //循环处理字节数组中的每一个字节
        for ( j = 0; j < data.length; j++)
        {
            //字节逆序(一个字节占8位 我们从寄存器的最高位处理数据 所以数据左移8)写入寄存器需要与原值进行一次异或
            crci ^= (CrcCheckTool.reverse8BITS(data[j])<<8);
            for ( i = 0; i < 8; i++){
                //判断即将左移 出的是不是1,如果是1则与多项式进行异或。(此处个人认为和小学学的除法算法中在结果上写1或者0类似)
                if ((crci & 0x8000) == 0x8000){
                    crci <<= 1;
                    //将数据与约定的多项式做(模二除/异或)
                    crci ^= 0x8005;
                }else{
                    crci <<= 1;
                }
            }
        }
        //结果值按位反转(结果值是一定小于多项式的16位,所以按位反转跟8位的方法不同) 然后与结果异或值异或
        String res  = Integer.toHexString(CrcCheckTool.reverse16BITS(crci)^0x0000);
        return res;
    }

    //将16进制字符串转换为byte[]
    public static byte[] hex2Bytes(String str) {
        if(str == null || str.trim().equals("")) {
            return new byte[0];
        }
        byte[] bytes = new byte[str.length() / 2];
        for(int i = 0; i < str.length() / 2; i++) {
            String subStr = str.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }
        return bytes;
    }

    //8位逆序反转 (逆序反转的方式有很多,之前看的帖子说这样运算最快,未实际验证)
    private static int reverse8BITS(int b){
        //交换1位
        b =(b & 0xaa) >> 1 | (b & 0x55) << 1;
        //交换2位
        b =(b & 0xcc) >> 2 | (b & 0x33) << 2;
        //交换4位
        b =(b & 0xf0) >> 4 | (b & 0x0f) << 4;
        return b;
    }

    //16位逆序反转
    private static int reverse16BITS(int b){
        //交换1位
        b =(b & 0xaaaa) >> 1 | (b & 0x5555) << 1;
        //交换2位
        b =(b & 0xcccc) >> 2 | (b & 0x3333) << 2;
        //交换4位
        b =(b & 0xf0f0) >> 4 | (b & 0x0f0f) << 4;
        //交换8位
        b =(b & 0xff00) >> 8 | (b & 0x00ff) << 8;
        return b;
    }

五、CRC16_MODBUS逆向校验代码

网上对此种模式的校验很多,但大多是以逆序校验的方式进行的,与CRC的标准要求算法不同,我最开始分析的时候,很难看懂。
但是如果,仔细研究CRC校验的设计思想,实际操作是把原始数据整体看成二进制序列,统一进行的一次循环求余操作,其实与字节的左移处理与右移处理无关,当字节做右移处理时,代码如下

 //逆序校验
    public static String CRC16Right(String dataHexStr)
    {
        //字符串类型的16进制字符串转换为字节数组
        byte[] data = DataModelFormatTool.hex2Bytes(dataHexStr);
        //初始化寄存器 我们用数据的低八位进行处理,已经可以不用限制寄存器的个数了
        //我测试了int long 都能达到预期的效果 但是short 反而数据错误 没想明白为什么。
        int crci = 0xffff;
        int i,j;
        for ( j = 0; j < data.length; j++)
        {
            //此处&0x00ff的目的是为了将数据放入寄存器的低8位并与寄存器异或
            crci ^=  data[j] & 0x00ff;
            for ( i = 0; i < 8; i++){
                //判断即将右移的是不是1 如果是1则与多项式进行异或
                if ((crci & 0X0001) == 0X0001){
                    crci >>= 1;
                    //将数据与约定的多项式做(模二除/异或)  数据流向变了此处的异或值实际是8005的逆序反转a001
                    crci ^= 0xa001;
                }else{
                    crci >>= 1;
                }
            }
        }
        //结果值与0x0000异或 其实值不会改变,不异或对结果没有影响
        String res  = Integer.toHexString( crci^ 0x0000) ;
        return res;
    }

你会发现逆序校验减少了多次的逆序操作,只需要事先计算一次多项式的逆序值就可以了,对于整体的数据位来说,实际数据预算过程与结果没有变化,故逆序操作的实现方式更省资源。
六、crc的意义在于校验数据传输过程中无法规避的数据变化,通过事先约定好的校验多项式,来确保帧的数据格式正确。常见的crc校验模型有很多,如果不必要,不建议自己定义校验多项式,毕竟不用于加密,标准的多项式某种程度上能检验出更多的数据传输位错误。

七、感谢我的大学同学谢宁宁同学在数学方面对我的支持和帮助,并附加一些我查看的网址资料,如果有错误或者能帮我对逆序校验中的crci变为short类型会数据校验错误的问题解惑,请随时私信我。
参考依据:https://www.cnblogs.com/skullboyer/p/8342167.html   (转) crc16几种标准校验算法及c语言代码

                 https://blog.csdn.net/scmuzi18/article/details/71641772   如何进行crc校验

                https://wenku.baidu.com/view/75f880a85f0e7cd1852536ac.html  循环码(看了前两部分)
                 https://blog.csdn.net/pl0020/article/details/82760761            CRC计算流程分析
                https://blog.csdn.net/haowwuu/article/details/78931566         CRC校验java版
                https://blog.csdn.net/jakee304/article/details/2152655        对一个字符按bit位逆序(又称反转)

       

你可能感兴趣的:(crc冗余校验原理,CRC校验,CRC16_MODBUS,JAVA)