Checksum
Checksum
概念
检验和 (checksum),在数据处理和数据通信领域中,用于 校验目的 的一组数据项的和,用来校验数据的完整性和准确性。
一般的checksum计算方法
-
把要计算checksum的数据内容分成以每两个字节为一组的分组。如果最后剩余单个字节,补一个内容为0的字节。
如数据:hello world
String: hello world h e l l o '空格' w o r l d HEX: 68 65 6C 6C 6F 20 77 6F 72 6C 64 BIN: 0110 1000 0110 0101 0110 1100 0110 1100 0110 1111 0010 0000 0111 0111 0110 1111 0111 0010 0110 1100 0110 0100
分组:每16位一组
h e 0110 1000 0110 0101 l l 0110 1100 0110 1100 o '空格' 0110 1111 0010 0000 w o 0111 0111 0110 1111 r l 0111 0010 0110 1100 d 0110 0100
-
将所有分组(16bit)累加起来,累加过程中,如果结果溢出(超过16bit),将溢出的位作为新的分组累加上去
h e 0110 1000 0110 0101 l l + 0110 1100 0110 1100 ------------------------------------- = 1101 0100 1101 0001 o '空格' + 0110 1111 0010 0000 ------------------------------------- = 1 0100 0011 1111 0001 #溢出 #累加溢出位 0100 0011 1111 0001 + 0000 0000 0000 0001 #溢出数据 ------------------------------------- = 0100 0011 1111 0010 w o + 0111 0111 0110 1111 ------------------------------------- = 1011 1011 0110 0001 r l + 0111 0010 0110 1100 ------------------------------------- = 1 0010 1101 1100 1101 #溢出 #累加溢出位 0010 1101 1100 1101 + 0000 0000 0000 0001 #溢出数据 ------------------------------------- = 0010 1101 1100 1110 d 0 + 0110 0100 0000 0000 #补一个为0的字节 ------------------------------------- = 1001 0001 1100 1110 ~ ------------------------------------- 0110 1110 0011 0001 #取反后的值即为 checksum hex: 6E31
-
将最终累加得到的16bit值按位取反,得到checksum的值
1001 0001 1100 1110
按位取反得到:0110 1110 0011 0001 ==> 6E31
C 语言实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <endian.h>
#include <arpa/inet.h>
/**
* @Name - 计算校验和
* @Parameter *data - 要计算校验和的数据,非空指针
* @Parameter len - 要计算校验和的数据长度
*/
uint16_t cksum(unsigned char *data, int len)
{
unsigned int sum = 0;
uint16_t cksum = 0;
uint16_t tval = 0;
/* 每两个字节进行累加 */
while (len > 1) {
sum += (*((uint16_t *)data));
len -= sizeof(uint16_t);
data += sizeof(uint16_t);
}
if (len > 0) { /* 每两个字节分组后剩余 1 byte */
*((unsigned char *)&tval) = *data;
sum += tval;
/* 两种方式均可 */
// #if __BYTE_ORDER == __LITTLE_ENDIAN
// sum += *data;
// #else
// sum += ((*data << 8) & 0xff00);
// #endif
}
/* 把溢出位累加到sum */
while(sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
cksum = sum & 0xffff;
return (~cksum);
}
int main(int argc, char *argv[])
{
uint16_t csum = 0;
char *str = "hello world";
unsigned char data[] = { //hello world
0x68,0x65,
0x6C,0x6C,
0x6F,0x20,
0x77,0x6F,
0x72,0x6C,
0x64
};
csum = cksum(str, strlen(str));
printf("cksum=0x%x net byte order cksum = 0x%x\n", csum, htons(csum));
csum = cksum(data, sizeof(data));
printf("cksum=0x%x net byte order cksum = 0x%x\n", csum, htons(csum));
return 0;
}
run
小端机上编译运行:
❯ ./a.out
cksum=0x316e net byte order cksum = 0x6e31
cksum=0x316e net byte order cksum = 0x6e31
大端机上编译运行:
# ./endian
cksum=0x6e31 net byte order cksum = 0x6e31
cksum=0x6e31 net byte order cksum = 0x6e31
应用
如UDP数据报中的应用
- 发送方在发送数据报之前,把checksum字段清空,然后计算整个报文的checksum值,把checksum值放入checksum字段
- 接收方收到这个数据之后,使用同样的checksum算法,计算整个checksum,如果数据发送没有出错,则checksum计算出来应该是 0
~checksumval是源数据中checksum字段清空后的所有数据的累加和
checksumval是checksum字段的值
两部分累加后取反即是收到的数据的checksum
即:(checksum + checksum) = 0
图示理解: