题目与要求
编写一个计算机程序用来计算一个文件的 16 位效验和。最快速的方法是用一个 32 位的 整数来存放这个和。记住要处理进位(例如,超过 16 位的那些位),把它们加到效验和中。
要求:
1)以命令行形式运行:check_sum infile
其中 check_sum 为程序名,infile 为输入数据文件名。
2)输出:数据文件的效验和
附:效验和(checksum)
参见 RFC1071 - Computing the Internet checksum
- 原理:把要发送的数据看成 16 比特的二进制整数序列,并计算他们的 和。若数据字
节长度为奇数,则在数据尾部补一个字节的 0 以凑成偶数。
29 - 例子:16 位效验和计算,下图表明一个小的字符串的 16 位效验和的计算。
为了计算效验和,发送计算机把每对字符当成 16 位整数处理并计算效验和。如果效验和大于 16 位,那么把进位一起加到最后的效验和中.
检验和原理
检验和原理,即为IP数据报首部校验和,其计算采用16位二进制反码求和算法。
步骤:
将数据报首部按字(16位)进行反码运算求和,获取的和取反码后即为检验和。发送端发送数据报时将检验和带上,接收端接受时,再次计算检验和,若结果为0则保留;否则丢弃该数据报。
参考代码
public class Main {
public static void main(String[] args) {
try {
if(args == null || args.length != 1 ) {
System.out.println("使用方式:java class名 文件路径");
} else if(args.length == 1){
System.out.println("读取"+args[0]+"...");
System.out.println("检验码为:"+check_sum(args[0]));
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("出现异常,已退出!");
}
}
/**
* 计算文件校验和
* @param path 文件路径
* @return 校验和
*/
public static String check_sum(String path) throws IOException {
// 计算总和,
int sum = 0;
// 读取文件流
File file = new File(path);
FileInputStream inputStream = new FileInputStream(file);
// 字节数组,2个字节,代表16位
byte[] b = new byte[2];
// 读取的字节数
int n;
while((n = inputStream.read(b)) != -1) {
if(n == 1) {
// 读取字节数为奇数,补齐8个0(1字节)
sum += b[0] << 8;
} else {
// 读取字节数为偶数
sum += (b[0] << 8) + b[1];
}
}
inputStream.close();
// 进位处理
if(sum > 0xffff) {
sum = (sum / 65536 + sum % 65536);
}
// 取反码 java的int占32位,而校验码为16位
return Integer.toHexString(~sum).substring(4);
}
}
关于文件读取的问题:这里建议用字节流读取,因为java中的String是2个字节的,也就是16位。而题目这里要求的是两个字组成16位。用字符流读取不仅要进行字节的转换,还容易出错。使用其他编程语言实现也同理,建议使用字节流。
运行
首先在桌面创建一个文本文件:
然后打开命令行编译运行程序:
java Main
中的Main
为程序入口的类
检验运算结果
71FC: 0111 0001 1111 1100
取反: 1000 1110 0000 0011
即: 8 e 0 3
模拟客户端接收:
4865+6C6C+6F20+776F+726C+642E+8E03(检验码) = 2FFFD
2FFFD进行反码处理:2FFFD / 0x10000 + 2FFFD % 0x10000 = 0xFFFF
取反:0x0000,即为0,数据检验成功!