php实现crc16算法

crc(循环冗余校验)是一种常用来检验数据完整性和正确性的算法,常用于网络传输校验,压缩算法等等,简单来说,crc把一个待校验字符串当作一个非常大的整数,然后除以一个特定的数,所得的余数就是crc校验值,只不过在进行除法运算时,对二进制数加减采用模二运算,也即异或运算,详细的crc介绍请参考:https://www.lammertbies.nl/comm/info/crc-calculation.html

crc校验根据除数的二进制位数,通常有crc-8,crc-16,crc-32等,同时根据这个除数的选择,以及初始值,结果异或值,输入是否按比特位反转,输出是否按比特位反转,同一个位宽的crc算法又分为各种不同的实现,例如:

CRC-16/CCITT:除数选定为 0x1021,初始值为0,最终结果输出前异或0,输入的每个字节按比特位反转,结果在最终异或前按比特位反转

CRC-16/MODBUS:除数选定为 0x8005,初始值为 0xffff,最终结果输出前异或0, 输入的每个字节按比特位反转,结果在最终异或前按比特位反转。

php原生提供了crc32方法,但没有提供crc16方法,在效率要求不高的情况下,我们可以使用php来实现crc16算法:

/**
 * 将一个字符按比特位进行反转 eg: 65 (01000001) --> 130(10000010)
 * @param $char
 * @return $char
 */
function reverseChar($char) {
    $byte = ord($char);
    $tmp = 0;
    for ($i = 0; $i < 8; ++$i) {
        if ($byte & (1 << $i)) {
            $tmp |= (1 << (7 - $i));
        }
    }
    return chr($tmp);
}

/**
 * 将一个字节流按比特位反转 eg: 'AB'(01000001 01000010)  --> '\x42\x82'(01000010 10000010)
 * @param $str
 */
function reverseString($str) {
    $m = 0;
    $n = strlen($str) - 1;
    while ($m <= $n) {
        if ($m == $n) {
            $str{$m} = reverseChar($str{$m});
            break;
        }
        $ord1 = reverseChar($str{$m});
        $ord2 = reverseChar($str{$n});
        $str{$m} = $ord2;
        $str{$n} = $ord1;
        $m++;
        $n--;
    }
    return $str;
}

/**
 * @param string $str 待校验字符串
 * @param int $polynomial 二项式
 * @param int $initValue 初始值
 * @param int $xOrValue 输出结果前异或的值
 * @param bool $inputReverse 输入字符串是否每个字节按比特位反转
 * @param bool $outputReverse 输出是否整体按比特位反转
 * @return int
 */
function crc16($str, $polynomial, $initValue, $xOrValue, $inputReverse = false, $outputReverse = false) {
    $crc = $initValue;

    for ($i = 0; $i < strlen($str); $i++) {
        if ($inputReverse) {
            // 输入数据每个字节按比特位逆转
            $c = ord(reverseChar($str{$i}));
        } else {
            $c = ord($str{$i});
        }
        $crc ^= ($c << 8);
        for ($j = 0; $j < 8; ++$j) {
            if ($crc & 0x8000) {
                $crc = (($crc << 1) & 0xffff) ^ $polynomial;
            } else {
                $crc = ($crc << 1) & 0xffff;
            }
        }
    }
    if ($outputReverse) {
        // 把低地址存低位,即采用小端法将整数转换为字符串
        $ret = pack('cc', $crc & 0xff, ($crc >> 8) & 0xff);
        // 输出结果按比特位逆转整个字符串
        $ret = reverseString($ret);
        // 再把结果按小端法重新转换成整数
        $arr = unpack('vshort', $ret);
        $crc = $arr['short'];
    }
    return $crc ^ $xOrValue;
}

 下面列出一些常用的crc16算法实现,基本是一些大厂所采用的或者是某某协议中用到的,可以使用网站 http://www.ip33.com/crc.html 进行在线计算

/* 列举一些常用的crc16算法 */

// CRC-16/IBM
printf("%x\n", crc16('1234567890', 0x8005, 0, 0, true, true));

// CRC-16/MAXIM
printf("%x\n", crc16('1234567890', 0x8005, 0, 0xffff, true, true));

// CRC-16/USB
printf("%x\n", crc16('1234567890', 0x8005, 0xffff, 0xffff, true, true));

// CRC-16/MODBUS
printf("%x\n", crc16('1234567890', 0x8005, 0xffff, 0, true, true));

// CRC-16/CCITT
printf("%x\n", crc16('1234567890', 0x1021, 0, 0, true, true));

// CRC-16/CCITT-FALSE
printf("%x\n", crc16('1234567890', 0x1021, 0xffff, 0, false, false));

// CRC-16/X25
printf("%x\n", crc16('1234567890', 0x1021, 0xffff, 0xffff, true, true));

// CRC-16/XMODEM
printf("%x\n", crc16('1234567890', 0x1021, 0, 0, false, false));

// CRC-16/DNP
printf("%x\n", crc16('1234567890', 0x3d65, 0, 0xffff, true, true));

以上运行结果:

c57a
3a85
3df5
c20a
286b
3218
4b13
d321
bc1b

 

作为对比,再用php实现一下自己的crc32函数,再和php自带的crc32函数计算结果进行对比

 

function php_crc32($str) {
    $polynomial = 0x04c11db7;
    $crc = 0xffffffff;
    for ($i = 0; $i < strlen($str); $i++) {
        $c = ord(reverseChar($str{$i}));
        $crc ^= ($c << 24);
        for ($j = 0; $j < 8; $j++) {
            if ($crc & 0x80000000) {
                $crc = (($crc << 1) & 0xffffffff) ^ $polynomial;
            } else {
                $crc = ($crc << 1) & 0xffffffff;
            }
        }
    }
    $ret = pack('cccc', $crc & 0xff, ($crc >> 8) & 0xff, ($crc >> 16) & 0xff, ($crc >> 24) & 0xff);
    $ret = reverseString($ret);
    $arr = unpack('Vret', $ret);
    $ret = $arr['ret'] ^ 0xffffffff;
    return $ret;
}

var_dump(php_crc32('1234567890') === crc32('1234567890'));
var_dump(php_crc32('php是世界上最好的语言') === crc32('php是世界上最好的语言'));

 运行结果:

bool(true)
bool(true)

简单对比下计算效率:

<?php

$fp = fopen('/dev/urandom', 'r');
$str = '';
for ($i = 0; $i < 1024; ++$i) {
    $str .= fread($fp, 1024);
}

// 1Mb数据
echo "string length: " . strlen($str) . PHP_EOL;
fclose($fp);

$start1 = microtime(true);
echo 'my crc32:' . php_crc32($str) . PHP_EOL;
echo 'my crc32消耗时间:' . (microtime(true) - $start1) . PHP_EOL;

echo "----------------------------------------------------------------------\n";

$start2 = microtime(true);
echo 'crc32: ' . crc32($str) . PHP_EOL;
echo 'crc32消耗时间:' . (microtime(true) - $start2) . PHP_EOL;

结果:

string length: 1048576
my crc32:3171559529
my crc32消耗时间:5.8124451637268
----------------------------------------------------------------------
crc32: 3171559529
crc32消耗时间:0.0083820819854736

结果相同,但性能差距在三个数量级。

以上实现虽然基本上都是位运算,但对于比较长的字符串,运算量还是比较大,实际crc实现一般采用事先生成好的数组直接查表,能够减少大量的计算,感兴趣可以自己去研究一下。

That's all

 

 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值