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