⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 framehandler.cpp

📁 对以太帧进行手动封装
💻 CPP
📖 第 1 页 / 共 2 页
字号:
#include "framehandler.h"
#include <iostream>
#include <cstring>
#include <iomanip>

using std::cout;
using std::endl;
using std::ios_base;
using std::setw;
using std::cin;
using std::ios;



////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  构造函数
//	参数:
//		  bool isPackging : 该参数表示当前实例要执行何种操作,为true表示[帧封装],为false表示[帧解析]
//		  const char* filePath : 根据isPackaging的不同,该参数有不同意义
//		   					     当前者为true时,则将封装好的帧保存到filePath指定的文件中
//							     当前者为false时,则从filePath指定的文件中解析Ethernet帧
////////////////////////////////////////////////////////////////////////////////////////////////////////////

FrameHandler::FrameHandler(bool isPackaging, const char* filePath)
{
	// 设置操作类型,true表示帧封装,false表示帧解析
	this->isPackaging = isPackaging;

	// 保存文件路径
	// 当进行帧封装时,表示封装后的帧所存放的文件路径;当进行帧解析时,表示待解析的文件的路径
	this->filePath = new char[strlen(filePath) + 1];
	strcpy(this->filePath, filePath);

	// 对hexAlpha[]数组进行赋值
	for (int i = 0; i < 10; i++)
	{
		hexAlpha[i]  = char ('0' + i);
	}
	for (i = 10; i < 16; i++)
	{
		hexAlpha[i] = char ('A' + i - 10);
	}
}



////////////////////////////////////////////////////////////////////////////////////////////////
//  析构函数
////////////////////////////////////////////////////////////////////////////////////////////////

FrameHandler::~FrameHandler()
{
	// 释放资源
	if (filePath != NULL)
	{
		delete[] filePath;
	}
}



///////////////////////////////////////////////////////////////////////////////////////////////
//  该函数为对外接口,可被FrameHandler的实例直接调用
//  功能:
//			完成[帧封装]操作,并将封装后的帧保存在filePath指定的文件中
//  参数:
//			void
//  返回值:
//			如果封装成功,则返回true
//			如果封装失败,则返回false
///////////////////////////////////////////////////////////////////////////////////////////////

bool FrameHandler::package()
{
	// 如果创建实例时指定的操作类型不是“帧封装”,则调用该函数失败
	if (isPackaging == false)
	{
		return false;
	}

	// 下面进行帧封装的操作

	// 输出操作信息
	cout << "操作类型:帧封装\n保存封装结果的文件路径:" << filePath 
		 << "\n\n==================================================\n\n";

	// 让用户输入一段信息,以回车结束
	cout << "请输入一段信息,以回车结束\n(如果直接输入回车,则可以从文件中读取数据,并将其封装成Ethernet帧):" << endl;
	char* data = new char[MAX_DATA_LENGTH + 1];
	cin.getline(data, MAX_DATA_LENGTH + 1);

	// 如果用户直接输入了回车(即data中不含任何数据),则认为用户要从文件中加载数据,提示用户输入文件的路径
	if (strlen(data) == 0)
	{
		// 从文件中加载数据
		if (!loadDataFromFile(data))
		{
			cout << "\n从文件中加载数据失败!\n";
			delete[] data;
			return false;
		}
		else
		{
			cout << "\n==================================================\n"
				 << "从文件中读入的数据如下:\n\n" << data << endl;
		}
	}

	// 填充前导符、帧前定位符、目的MAC、源MAC、数据长度等信息
	FrameFront frameFront;
	setFrameFront(frameFront, data);
	
	// 计算CRC校验码(需要用到目的MAC、源MAC、数据长度、数据段、以及填充字节(如果有的话))
	int totalBytes = (strlen(data) < MIN_DATA_LENGTH) ? MIN_DATA_LENGTH : strlen(data);
	unsigned char* dataWithFillBytes = new unsigned char[totalBytes];
	memcpy(dataWithFillBytes, data, strlen(data));
	if (strlen(data) < MIN_DATA_LENGTH)
	{
		memset(dataWithFillBytes + strlen(data), 0, MIN_DATA_LENGTH - strlen(data));
	}
	unsigned char crc = getCRC(frameFront, dataWithFillBytes);

	// 将Ethernet帧保存到文件filePath中
	if (!storeFrameInFile(frameFront, data, crc))
	{
		cout << "\n向文件 [" << filePath << "] 写入数据失败!\n";
		return false;
	}
	else
	{
		// 写入成功,释放所有资源并返回true
		delete[] data;
		cout << "\n==================================================\n";
		cout << "帧封装完毕!\n封装后的Ethernet帧存放在文件 [" << filePath << "] 中!\n\n";
		return true;
	}
}



////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  该函数为对外接口,可被FrameHandler的实例直接调用
//  功能:
//			完成[帧解析]操作,将filePath指定的文件进行解析,从中提取出Ethernet帧的相关信息
//  参数:
//			void
//  返回值:
//			如果分析文件成功,则返回true
//			如果在处理文件的过程中出现任何错误,则返回false
////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool FrameHandler::unpackage()
{
	// 如果创建实例时指定的操作类型不是“帧解析”,则调用该函数失败
	if (isPackaging == true)
	{
		return false;
	}

	// 下面进行帧解析的操作

	// 以二进制读的方式打开待解析的文件
	FILE* file = fopen(filePath, "rb");

	// 如果打开文件失败,则输出错误信息并返回false值
	if (NULL == file)
	{
		cout << "\n无法打开文件 [" << filePath << "] !请确认文件路径是否正确!\n";
		return false;
	}

	// 如果成功的打开了文件,则执行下面的操作
	
	// 输出提示信息
	cout << "操作类型:Ethernet帧解析\n待解析的文件路径:" << filePath << endl;

	int frameCounter = 0;  // 记录已经解析的帧的数目
	FrameFront frameFront;  // 保存读入的帧的前面部分(即前导符、界定符、目的mac、源mac、数据长度)
	int count = sizeof(frameFront) / sizeof(unsigned char);  // FrameFront结构体包含的字节数

	// 从文件中读取数据并分析,直到文件尾或者发生错误
	while (count == fread(&frameFront, sizeof(unsigned char), count, file))
	{
		if (isLegalPreamble(frameFront.preamble))
		{
			frameCounter++;
			cout << "\n=========================第" << frameCounter << "个帧=========================\n";
				
			// 设定输出格式为左对齐
			cout.setf(ios::left);
			// 输出帧的序号
			cout << setw(WIDTH) << "序号" << ":" << frameCounter << endl;

			// 输出前导码、帧前定位符、目的地址、源地址、数据长度等
			outputFrameFront(frameFront);

			// 计算数据段的长度(不包括填充字节)
			int dataLength = (frameFront.dataLength[0] << 8) + frameFront.dataLength[1];

			// 计算数据段与填充字节的总长度
			int totalBytes = (dataLength < MIN_DATA_LENGTH) ? MIN_DATA_LENGTH : dataLength;
			
			// 读取数据段与填充字节(如果有的话)
			unsigned char* data = new unsigned char[totalBytes];
			fread(data, sizeof(unsigned char), totalBytes, file);

			// 输出数据段的有效内容(不包括填充字节)
			cout << setw(WIDTH) << "数据字段" << ":";
			char* dataToShow = new char[dataLength + 1];
			memcpy(dataToShow, data, dataLength);
			dataToShow[dataLength] = 0;
			cout << dataToShow << endl;
			delete[] dataToShow;

			// 获取帧中的CRC校验字段
			unsigned char crcInFrame;
			fread(&crcInFrame, sizeof(unsigned char), 1, file);

			// 计算CRC校验码(需要用到目的MAC、源MAC、数据长度、以及具体的数据段)
			unsigned char realCRC = getCRC(frameFront, data);

			// 输出CRC校验信息,以及是否接收等
			cout << setw(WIDTH) << "CRC校验" << ":";
			if (crcInFrame == realCRC)
			{
				cout << "(正确):" << hexAlpha[crcInFrame >> 4] << hexAlpha[crcInFrame & 0x0F] << endl;
				cout << setw(WIDTH) << "状态" << ":Accept" << endl;
			}
			else
			{
				// 输出 (错误):[原校验码] 应为 [正确的校验码]
				cout << "(错误):[" << hexAlpha[crcInFrame >> 4] << hexAlpha[crcInFrame & 0x0F] << "] 应为 ["
					 << hexAlpha[realCRC >> 4] << hexAlpha[realCRC & 0x0F] << "]" << endl;
				cout << setw(WIDTH) << "状态" << ":Discard" << endl;
			}				

			// 释放资源
			delete[] data;
				
			// 继续寻找下一个帧
			continue;
		}
		else
		{
			fseek(file, -(count - 1), ios_base::cur);
		}
	}

	// 解析完毕,输出发现的帧的数目
	if (frameCounter == 0)
	{
		cout << "\n==================================================\n";
		cout << "解析完毕,但没有发现任何Ethernet帧!\n\n";
	}
	else
	{
		cout << "\n==================================================\n";
		cout << "解析完毕,共找到上述" << frameCounter << "个Ethernet帧!\n\n";
	}

	// 关闭文件,然后返回
	fclose(file);
	return true;
}



/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  内部辅助函数
//  功能:
//			检查读入的数据是否包含正确的前导符以及帧前定位符,正确值应为十六进制的AA-AA-AA-AA-AA-AA-AA-AB
//  参数:
//		    const unsigned char* preamble : 该参数为传入值,表示7个字节的前导码 + 1个字节的帧前定位符
//  返回值:
//			如果前导码为十六进制的AA-AA-AA-AA-AA-AA-AA,且 帧前定位符为十六进制的AB,则返回true
//			否则返回false
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool FrameHandler::isLegalPreamble(const unsigned char* preamble)
{
	// 首先检查前7个字节是否为十六进制的AA-AA-AA-AA-AA-AA-AA
	for (int i = 0; i < 6; i++)
	{
		if(preamble[i] != 0xAA)
		{
			return false;
		}
	}

	// 再检查最后一个字节是否为十六进制的AB
	return (preamble[7] == 0xAB) ? true : false;
}



⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -