2022面向对象程序设计(福州大学)寒假作业2


这个作业属于哪个课程 2022面向对象程序设计 (福州大学)
这个作业要求在哪里 2022面向对象程序设计(福州大学)寒假作业2
这个作业的目标 路由器根据相应规则对数据包进行操作,现给出规则集及输入数据包,输出相应数据包最佳匹配的规则
作业正文 见下文
其他参考文献 IP地址分类及CIDR划分方法
VS2015中使用fstream类时 “error C2280尝试使用已删除的函数”的处理方法

Github仓库请点击这里 C和C++的程序完整源代码均已上传到GitHub仓库

完成本次作业需要学习的内容

  • 如何从文件输入数据以及将数据输出到文件
  • 不同源文件之间如何关联到同一个程序中
  • 对于IP地址块CIDR表示法的理解

学习过程

从文件读取数据和将数据输出到文件(Visual Studio 2022编译器,c文件):

/*从文件读取数据和将数据输出到文件*/
#include 
#include 

int main()
{
	FILE* fp, * out;
	char z[9854] = {};
	char input[2022] = {};//存储数据来源文件的完整路径
	char output[2022] = {};//存储数据输出文件的完整路径
	int i;
	printf("请输入数据来源文件的完整路径:\n");
	gets(input);
	printf("请输入数据输出文件的完整路径:\n");
	gets(output);
	fp = fopen(input, "r");//“r”表示以只读模式打开文件
	out = fopen(output, "w+");//“w+”表示打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
	while ((fscanf(fp, "%s", z)) != EOF)//从文件中按行读取相应类型数据,EOF表示读取到文件末尾后结束循环
	{
		fprintf(out, "%s\n", z);//将相应类型数据输出到文件
	}
	fclose(fp);//关闭文件指针对应文件
	fclose(out);
	system("pause");
	return 0;
}

这段程序完全使用C语言,利用C语言中的fopen、fclose、fscanf、fgets、fprintf等文件函数实现。
完成C语言代码后,我也尝试使用C++的fstream中对于文件的一些操作,并取得成功,其核心部分如下(Visual Studio 2022编译器,cpp文件):

#include 
#include 

char rule_packet[9854] = {};//存储规则集文件的完整路径
char packet[9854] = {};//存储数据集文件的完整路径
char output[9855] = {};//存储数据匹配结果文件的完整路径

cout << "请输入规则集文件的完整路径(可含空格,不含引号):" << endl;
gets_s(rule_packet);
cout << "请输入数据集文件的完整路径(可含空格,不含引号):" << endl;
gets_s(packet);
cout << "请输入数据匹配结果文件的完整路径,若文件不存在,则会自动新建文件(可含空格,不含引号):" << endl;
gets_s(output);

ifstream fp_rule(rule_packet, ios::in | ios::_Nocreate);//in表示从文件输入,_Nocreate表示若文件不存在,则不新建文件
fp_rule >> a >> b >> c;
ifstream fp_packet(packet, ios::in | ios::_Nocreate);
ofstream fp_out(output, ios::out);//out表示输出到文件,且文件原本的内容将会被清空
fp_out << a << b << c;

关于IP地址的CIDR表示法,我主要参考这篇CSDN博客文章学习的:IP地址分类及CIDR划分方法
C++中类的基本使用:

class rule
{
public:
	int in(ifstream& fp_rule);//fstream的拷贝(赋值)构造函数是已删除函数,“&”表示是将函数的形参设置为引用类型,防止报错
	class rule* create_chain_table(ifstream& fp_rule);//通过成员函数实现对私有部分变量的调用,成员函数必须在公有部分才能被调用
	void visit_chain_table(rule* p);
	void visit_processed_chain_table(rule* p, ifstream& fp_packet, ofstream& fp_out);
	
private:
	char ip0bin[37], ip1bin[37], z0[3], z1[3];
	unsigned int ip0min, ip0max, ip1min, ip1max;
	int ip01,ip02,ip03,ip04,ip0wei;
	int ip11,ip12,ip13,ip14,ip1wei;
	int d01, d02, d11, d12, x0, x1;
	char x;
	rule* next;
};
void rule::visit_processed_chain_table(rule* p, ifstream& fp_packet, ofstream& fp_out)
{
	unsigned int ip0, ip1, d0, d1, y, count, flag;
	rule* pt = p;//存放第一个结点的指针
	while (fp_packet >> ip0 >> ip1 >> d0 >> d1 >> y)
	{
		for (p = pt, count = 0, flag = 0; p->next; p = p->next)
		{
			if (rule_match(ip0, ip1, d0, d1, y, p->ip0min, p->ip0max, p->ip1min, p->ip1max, p->d01, p->d02, p->d11, p->d12, p->x0, p->x1))
			{
				fp_out << count << endl;
				flag = 1;
				break;
			}
			count++;//计数器,表示当前为第几条规则【规则从0开始编号】
		}
		if (!flag)fp_out << "-1\n";
	}
}
int main()
{
        rule* head, * p;
        p->visit_processed_chain_table(p, fp_packet, fp_out);
        return 0;
}

这里仅展示类的使用的部分代码,完整代码请移步GitHub仓库。

程序设计思路

将规则集的CIDR点分十进制IP地址转换为32个二进制数字存储在字符数组中,采用从右往左四个数字分别取余逆序填入字符数组,每8位二进制数为一组操作,不足8位的前面补0,具体代码如下:

void IP_Switch_From_Dotted_DEC_To_BIN(int ip1, int ip2, int ip3, int ip4, char ip_bin[])
{
	char* p;
	for (p = ip_bin + 32; ip4; ip4 /= 2, p--)
		*p = ip4 % 2 + '0';
	if (p != ip_bin + 24)
		for (; p > ip_bin + 24; p--)
			*p = '0';
	for (; ip3; ip3 /= 2, p--)
		*p = ip3 % 2 + '0';
	if (p != ip_bin + 16)
		for (; p > ip_bin + 16; p--)
			*p = '0';
	for (; ip2; ip2 /= 2, p--)
		*p = ip2 % 2 + '0';
	if (p != ip_bin + 8)
		for (; p > ip_bin + 8; p--)
			*p = '0';
	for (; ip1; ip1 /= 2, p--)
		*p = ip1 % 2 + '0';
	if (p != ip_bin)
		for (; p > ip_bin; p--)
			*p = '0';
}

之后利用位运算将32为二进制数字转换为一个十进制数,代码如下:

void IP_Switch_From_BIN_To_DEC(unsigned int *p,char x[])
{
	int i, m;
	*p = 0;
	for (i = 1, m = 31; i <= 32; i++, m--)
		*p += (x[i] - '0') * (1 << m);//利用位运算将二进制IP地址转换为十进制
}

然后根据IP地址的CIDR表示法中“/”之后的数字确定网络前缀位数,进而确实主机号位数,将主机号分别置0和置1可分别得到IP地址块的最小地址和最大地址。
再根据协议号表示及匹配规则确定可以匹配的协议号范围。
最后与数据包匹配,找到最佳匹配规则。
【注:完整的C++和C语言代码均已上传GitHub,此处不再展示】

测试程序

开始程序还存在严重bug无法输出正确结果,有时候实在找不到错误会使用逐语句调试模式,实时观察各个变量的值。
待程序严重bug修复、可以输出正确结果之后,对于程序的使用方式进行了部分完善,并进行调试。

C语言编写的程序调试过程:

C++编写的程序和简易文件比较程序调试过程:

计算程序匹配及输出过程的所用时间:

遇到的困难

  • 对于C++特色的类(class)的使用非常不熟练。解决办法:参考C++程序设计书本上的例子,自己先模仿书本例子编写一段程序调试,并尝试使用成员函数访问类中的私有部分
  • 链表以及文件流使用过程中遇到各种奇奇怪怪的问题。
  • 使用C++的类编写程序时,尝试将定义为ifstream的fp_rule和定义为ofstream的fp_out的变量作为参数传递给类成员函数是出现“尝试调用已删除函数”的报错。解决办法:参考CSDN这篇文章VS2015中使用fstream类时 “error C2280尝试使用已删除的函数”的处理方法,将形参改为引用类型(加上“&”)即可class rule* rule::create_chain_table(ifstream& fp_rule)

一些缺陷

  • 没有算法优化,纯粹暴力算法,程序匹配时间过长
  • 使用C++的类及类的链表时过于繁琐冗杂
  • 未能成功将C++编写的代码中类的相关模块独立成文件
  • 未能分析程序的时间复杂度
  • 未能安装题目要求实现利用命令行执行并输入文件,因此还是跟以前一样写了个交互式界面

本人能力有限,代码肯定还存在各种尚未发现的问题,欢迎大家批评指正。
Github仓库请点击这里 C和C++的程序完整源代码均已上传到GitHub仓库

相关