计网实验之16位校验和计算(C++)

522 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

前言

此题来自于计算机网络实验的一道题,此题思路理解不难,实现的难点在于进制的转换。

知识点:位运算、掩码运算、文件读写

题目——计算文件的16位校验和

编写一个计算机程序用来计算一个文件的16位校验和。最快速的方法是用一个32位的整数来存放这个和。记住要处理进位(例如,超过16位的那些位),把它们加到校验和中。 要求: 1)以命令行形式运行:check_sum infile 其中check_sum为程序名,infile为输入数据文件名。 2)输出:数据文件的效验和 附:校验和(checksum) 参见RFC1071 - Computing the Internet checksum  原理: 把要发送的数据看成16比特的二进制整数序列,并计算他们的和。若数据字节长度为奇数,则在数据尾部补一个字节的0以凑成偶数。  例子: 16位校验和计算,下图表明一个小的字符串的16位效验和的计算。 为了计算效验和,发送计算机把每对字符当成16位整数处理并计算校验和。如果校验和大于16位,那么把进位一起加到最后的校验和中。 在这里插入图片描述

实验原理分析与代码实现思路

①首先,生成一个名为test.txt的文件,并写入一段字符串"Hello World!"

②计算校验和

a)字符读取,并强制转化成ASCII码对应的序号。每次读取两个字符,直到文件结尾。当字符数量为奇数时,第二个字符为0。

b)采用位运算的方法,两个字符拼接成组。第一个字符的数值左移8位,相当于乘以2的8次方,与第二个字符的数值相加,得到的结果几位两个字母拼接之后的十进制数值,后面直接通过每组计算的数值进行计算即可。

c)十进制求和。将每一组的十进制数值相加,得到一个十进制整数checksum32 (int类型为32位,不用考虑求和后溢出问题)

d)进位处理与进制转换。通过掩码方法(位运算)可以将十进制数转换为二进制数,并加32位二进制分成高16位和低16位,再分别转成两个十进制数(对应的二进制16位)并相加得到真正的校验和checksum16。最后,通过位运算将其转换成16进制表示即可。

代码

#include <fstream>
#include <iostream>
using namespace std;

#define File "test.txt"

void writefile() {
	ofstream file;
	file.open(File);
	file << "Hello World!";
	file.close();
}

void checksum(const char* filename) {
	int checksum32 = 0;
	fstream file;
	file.open(File, ios::in);
	printf("读取的文件数据为:");
	while (!file.eof()) {
		char a = file.get();
		printf("%c", a);
		//printf("%d ", a);
		if (a == -1) break;  //到达文件结尾
		int b = a;
		//printf("%d ", b << 8);
		int hex;
		int d;
		char c = file.get();
		printf("%c", c);
		//printf("%d ", c);
		if (c == -1) {  //奇数个字符
			d = 0;
		}
		else {  //偶数个字符
			d = c;
		}
		hex = (b << 8) + d;  //注意加括号!!!
		checksum32 += hex;
		//printf("%d\n", hex);
	}
	printf("\n\n");
	printf("每两个字母为一组,组成16位。将每一组的10进制数求和,得到结果如下:\n");
	printf("32位校验和的10进制为:%d\n", checksum32);
	//通过掩码转化为2进制
	printf("32位校验和的2进制数为:");
	for (int i = 0; i < 32; ++i) {
		if (i == 16) printf(" ");
		int binaryKey = checksum32 & (1 << 31 - i);
		if (binaryKey != 0) binaryKey = 1;
		printf("%d", binaryKey);
	}
	int left;  //进位
	int right;
	left = checksum32 & (0xffff0000);
	left = left >> 16;
	right = checksum32 & (0x0000ffff);
	printf("\n\n");
	printf("将32位二进制拆分成高16位进位数,低16位非进位数,得到结果如下:\n");
	//printf("\nleft=%d  ", left);
	//printf("right=%d\n", right);
	//printf("\n");
	printf("高16位的二进制数为:");
	for (int i = 0; i < 16; ++i) {
		int binaryKey = left & (1 << 15 - i);
		if (binaryKey != 0) binaryKey = 1;
		printf("%d", binaryKey);
	}
	printf("\n");
	printf("低16位的二进制数为:");
	for (int i = 0; i < 16; ++i) {
		int binaryKey = right & (1 << 15 - i);
		if (binaryKey != 0) binaryKey = 1;
		printf("%d", binaryKey);
	}
	printf("\n\n");
	printf("将高位和地位的16进制相加,得到如下结果:\n");
	//printf("\n");
	int checksum16 = left + right;
	printf("16位校验和的10进制为:%d\n", checksum16);
	printf("16位校验和的2进制为:");
	for (int i = 0; i < 16; ++i) {
		int binaryKey = checksum16 & (1 << 15 - i);
		if (binaryKey != 0) binaryKey = 1;
		printf("%d", binaryKey);
	}
	printf("\n");
	//转化为16进制为:
	char checksumHex[5];
	int hex = 0;
	for (int i = 0; i <16; ++i) {
		hex = hex + (checksum16&(1 << (15 - i)));
		if ((i + 1) % 4 == 0) {
			hex = hex >> (15 - i);  //每四位的8421之和
			//printf("测试:%d\n", hex);
			if (hex < 10) checksumHex[i / 4] = hex + '0';
			else checksumHex[i / 4] = hex - 10 + 'A';
			hex = 0;
		}
	}
	checksumHex[4] = '\0';
	printf("\n16位校验和的16进制为:");
	printf("%s\n", checksumHex);
}

int main()
{
	//writefile();
	//char*filename;
	char*command;
	char*filename;
	command = new char[20];
	filename = new char[20];
	printf("请输入要执行的命令和处理的文件名(用空格分开):\n");
	cin >> command >> filename;
	//printf("输入的命令是:%s\n", command);
	if (!strcmp(command, "check_sum")) {  //字符串相等返回0
		
		//printf("输入的文件名为:%s\n", filename);
		try {
			fstream file;
			file.open(filename, ios::in);
			printf("读取文件成功!\n");
		}
		catch (...) {
			printf("文件打开失败,请确认文件是否存在!\n");
		}
		checksum(filename);
	}
	else {
		printf("输入命令无效!\n");
	}
	//checksum(File);
}

实验结果

文件的字符串采用题目给定的例子,代码最终输出的结果也与题目给定结果相同,验证了算法的正确性。

在这里插入图片描述