目录一、课程设计目的 (2)二、开发环境、运行方式 (5)1、开发环境 (5)2、运行方式 (5)3、测试结果截图 (6)三、流程的说明 (8)四、帧封装的过程 (10)1、填充帧头部字段 (10)2、填充数据字段 (10)3、CRC校验 (10)4、主程序设计 (12)五、帧封装方法的相关扩展 (21)1、比特型算法 (22)2、字节型算法 (22)六、课程设计心得与体会 (23)七、参考文献 (23)一、课程设计目的帧是网络通信的基本传输单元,熟悉帧结构对于理解网络协议的概念、协议执行过程以及网络层次结构具有重要的意义。
本次作业的目的是应用数据链路层与介质访问控制子层的知识,根据数据链路层的基本原理,通过封装和解析Ethernet 帧,了解Ethernet 帧结构中各个字段的含义,从而深入理解Internet 协议族中的最底层协议——数据链路层协议。
网络节点间发送数据都要将它放在帧的有效部分,分为一个或多个帧进行传送。
节点之间可靠的帧传输不仅是通信的保障,而且还可以实现网络控制等各种功能。
1980年,Xerox、DEC与Intel等三家公司合作,第一次公布了Ethernet的物理层、数据链路层规范;1981年Ethernet V2.0规范公布;IEEE 802.3 标准是在Ethernet V2.0规范的基础上制定的,IEEE 802.3针对整个CSMA/CD网络,它的制定推动了Ethernet 技术的发展和广泛应用。
Ethernet V2.0规范和IEEE802.3标准中的Ethernet帧结构有一些差别,这里我们按Ethernet V2.0的帧结构进行讨论。
图 1.1 IEEE802.3标准Ethernet帧结构802.3标准中Ethernet帧结构由以下几个部分组成:(1)前导码和帧前定界符前导码由56位(7Byte)的10101010…1010比特序列组成,帧前定界符由一个8位的字节组成,其比特序列位10101011。
前导码用于使接收端同步,不计入帧头长度。
帧前定界符也不计入帧头长度。
(2)目的地址和源地址目的地址与源地址均分别表示帧的接收结点与发送结点的硬件地址。
硬件地址一般称作MAC 地址或物理地址。
在Ethernet 帧中,目的地址和源地址字段长度可以是2B 或6B。
早期的Ethernet 曾经使用过2B 长度的地址,但是目前所有的Ethernet 都使用6B(即48 位)长度的地址。
为了方便起见,通常使用16 进制数书写(例如,00-13-d3-a2-42-a8)。
为了保证MAC地址的唯一性,世界上由一个专门的组织负责为网卡的生产厂家分配MAC地址。
Ethernet帧的目的地址可以分为以下3种。
●单播地址(unicast address):目的地址的第一位为0表示单播地址。
目的地址是单播地址,则表示该帧只被与目的地址相同的结点所接收。
●多播地址(multicast address):目的地址的第一位为1表示多播地址。
目的地址是多播地址,则表示该帧被一组结点所接接收。
●广播地址(broadcast address):目的地址为全1则表示广播地址。
目的地址是广播地址,则表示该帧被所有结点接收。
(3)数据长度字段802.3标准中的帧用2B定义LLC数据字段包含的字节数。
描述了LLC数据的实际长度。
(4)数据字段IEEE802.3 协议规定LLC 数据的长度在46B 与1500B 之间。
如果数据的长度少于46B,需要加填充字节,补充到46B。
填充字段是任意的,不计入长度字段值中。
帧头部分长度为18B,包括6B 的目的地址字段、6B 的源地址字段、2B 的长度字段、4B 的帧校验和字段,而前导码与帧前界定符不计入帧头长度中,那么,Ethernet 帧的最小长度为64B,最大长度为1518B。
设置最小帧长度的一个目的是使每个接收结点能够有足够的时间检测到冲突。
(5) 帧校验字段帧校验字段FCS 采用32位CRC 校验。
校验的范围包括目的地址字段、源地址字段、长度字段、LLC 数据字段。
在接收端进行校验,如果发现错误,帧将被丢弃。
在本次作业中,为了简便起见,采用8位的CRC 校验。
8位CRC 校验的生成多项式为:1)(128+++=x x x x GCRC 校验的工作原理是:将要发送的数据比特序列当作一个多项式f(x)的系数,在发送端用收发双方预先约定的生成多项式G(x)去除,求得一个余数多项式。
将余数多项式附在数据多项式之后发送到接收端。
在接收端用同样的生成多项是G(x)去除接受数据多项式f(x),得到计算余数多项式。
如果计算余数多项式与接受余数多项式不相同,则表示传输有差错;否则数据认为正确而被接受。
CRC 编码实际上是一个循环移位的模2运算,在加法中不进位,在减法中不借位,等价于操作数的按位异或(XOR )。
CRC 校验的实现:为了简便起见,我们在程序中采用8 位的CRC 校验。
8 位CRC 校验的生成多项式如式1 :G(x) = x8 + x2 + x1 +1 (1)图1.2 CRC ‐8 的基本实现就是一个用来计算CRC ‐8(x8 + x2 + x1 +1)的硬件电路实现方法,它由8 个移位寄存器和3 个加法器(异或单元)组成。
计算过程如下:(1) 编码或解码前将所有寄存器清零;2) 输入位作为最右边异或操作的输入之一,8 个寄存器上的移位操作同时进行,均为左移一位;3) 最左边寄存器中位作为所有三个异或操作的输入之一;4) 每次移位时,最右边的寄存器作为中间异或操作的输入之一,中间的寄存器作为最左边异或操作输入之一;5) 各个异或操作的结果作为它左边那个寄存器的移入位;6) 重复步骤 2 到6,每输入一个bit 就做一次移位操作,直到输入了所有要计算的数据为止。
这时这个寄存器组中的数据就是CRC-8 的结果。
二.程序的执行环境、运行方式、测试结果截图1、开发环境平台:Windows编程环境:VC 6.0语言:C++2、运行方式1)开始→运行→输入cmd→进入DOS界面2)改变当前目录到可执行程序所在的文件夹下3)用户输入命令,程序包含帧封装和帧解析两个部分的功能。
帧封装格式:[可执行文件名] –p [数据帧文件名]其中 -p 表示帧封装,数据帧文件名由用户自己拟定。
帧封装可以让用户输入任意一段信息,以两个回车作为结束,然后程序将这段信息作为帧的数据字段封装到数据帧文件中。
帧封装格式[可执行文件名] –u [数据帧文件路径]其中 -u 表示帧解析,数据帧文件路径表示要进行解析的数据帧文件路径,比如input1 文件和input2 文件的路径。
帧解析可以将包含帧的数据文件作为数据,从这些文件中读出帧,并对其各字段进行解析。
并通过重新计算CRC 校验和,判断该帧是否接受。
若校验和正确,则接受;否则,丢弃。
3、测试结果截图1)帧解析图2.3.1 帧解析图1图2.3.2 帧解析图2图2.3.3 帧解析图3图2.3.4帧解析图4 2)封装与发送图2.3. 5 帧封装与发送图5图2.3.6帧封装与发送图6图2.3.7 帧封装与发送图7四、帧封装与解析过程1、帧封装在封装过程中需要注意的是如果所封装的数据长度小于46B的时候,需要填充字符,使其整体长度等于46B,但是需要注意的是当对其进行校验和显示的时候并不需要填充的字符串,此时需要将其剔除。
if (data_length<46){向其填充46-data_length个字符;}package(start,data_length,data,fp);封装其中start为起始位置,data为数据存储的数组,fp为文件指针;for(data中前data_length个数据)crc=CRC(data[i],crc);for中将填充字符剔除。
2、帧解析在解析中需要注意,当数据长度小于46B的时候,在读完数据之后,需要读取46-data_length个字符之后的字符作为校验码。
for(等待46-data_length个字符)file.get();crc=file.get();3、CRC校验函数在图1.2中,CRC-8 的计算过程是,当寄存器R7 的移出位为1 时,寄存器组才和00000111进行XOR运算;移出位为0 时,不做运算。
每次寄存器中的数据左移后就需要从输入数据中读入一位新的数据,如果读入的新数据为1,则需要把寄存器R0 置为1,然后再判断寄存器组是否需要与00000111 进行XOR 操作。
具体实现的伪代码如下://register_8是一个8位的寄存器把register_8中的值置为0;在原始数据input后添加8各0;while(数据未处理完){if(register_8首位是1){register_8中的数据左移1位;if(从input中读入的新的数据为1){将register_8的最低位置1;}register_8 = register_8 XOR 00000111;}else{register_8中的数据左移1位;if(从input中读入的新的数据为1){将register_8的最低位置1;}}}在程序中我构造了一个函数CRC(unsigned char a,unsigned char b);其中a为所要计算校验码的字符,b为此时crc的值。
变量b可以看成一个寄存器,它的值为初始寄存器的值;a为这个寄存器变换的输入。
4、主程序的设计void main(int argc, char* argv[]){// 检测命令行参数的正确性if (argc != 2){cout << "请以帧封装包文件为参数重新执行程序" << endl;exit(0);}// 检测输入文件是否存在,并可以按所需的权限和方式打开ifstream file(argv[1], ios::in|ios::binary|ios::nocreate);if (!file.is_open()){cout << "无法打开帧封装包文件,请检查文件是否存在并且未损坏" << endl;exit(0);}// 变量声明及初始化int nSN = 1; // 帧序号int nCheck = 0; // 校验码int nCurrDataOffset = 22; // 帧头偏移量int nCurrDataLength = 0; // 数据字段长度bool bParseCont = true; // 是否继续对输入文件进行解析int nFileEnd = 0; // 输入文件的长度// 计算输入文件的长度file.seekg(0, ios::end); // 把文件指针移到文件的末尾nFileEnd = file.tellg(); // 取得输入文件的长度file.seekg(0, ios::beg); // 文件指针位置初始化cout.fill('0'); // 显示初始化cout.setf(ios::uppercase); // 以大写字母输出// 定位到输入文件中的第一个有效帧// 从文件头开始,找到第一个连续的“AA-AA-AA-AA-AA-AA-AA-AB”while ( true ){for (int j = 0; j < 7; j++) // 找7个连续的0xaa{if (file.tellg() >= nFileEnd) // 安全性检测{cout<<"没有找到合法的帧"<<endl;file.close();exit(0);}// 看当前字符是不是0xaa,如果不是,则重新寻找7个连续的0xaa if (file.get() != 0xaa){j = -1;}}if (file.tellg() >= nFileEnd) // 安全性检测{cout<<"没有找到合法的帧"<<endl;file.close();exit(0);}if (file.get() == 0xab) // 判断7个连续的0xaa之后是否为0xab {break;}}// 将数据字段偏移量定位在上述二进制串之后14字节处,并准备进入解析阶段nCurrDataOffset = file.tellg() + 14;file.seekg(-8,ios::cur);// 主控循环while ( bParseCont ) // 当仍然可以继续解析输入文件时,继续解析{// 检测剩余文件是否可能包含完整帧头if (file.tellg() + 14 > nFileEnd){cout<<endl<<"没有找到完整帧头,解析终止"<<endl;file.close();exit(0);}int c; // 读入字节int i = 0; // 循环控制变量int EtherType = 0; // 由帧中读出的类型字段bool bAccept = true; // 是否接受该帧// 输出帧的序号cout << endl << "序号:\t\t" << nSN;// 输出前导码,只输出,不校验cout << endl << "前导码:\t";for (i = 0; i < 7; i++) // 输出格式为:AA AA AA AA AA AA AA {cout.width(2);cout << hex << file.get() << dec << " ";}// 输出帧前定界符,只输出,不校验cout << endl << "帧前定界符:\t";cout.width(2); // 输出格式为:ABcout << hex << file.get();// 输出目的地址,并校验cout << endl << "目的地址:\t";for (i = 0; i < 6; i++) // 输出格式为:xx-xx-xx-xx-xx-xx {c = file.get();cout.width(2);cout<< hex << c << dec << (i==5 ? "" : "-");if (i == 0) // 第一个字节,作为“余数”等待下一个bit {nCheck = c;}else // 开始校验{checkCRC(nCheck, c);}}// 输出源地址,并校验cout << endl << "源地址:\t";for (i = 0; i < 6; i++) // 输出格式为:xx-xx-xx-xx-xx-xx {c = file.get();cout.width(2);cout<< hex << c << dec << (i==5 ? "" : "-");checkCRC(nCheck, c); // 继续校验}// 输出类型字段,并校验cout<<endl<<"类型字段:\t";cout.width(2);// 输出类型字段的高8位c = file.get();cout<< hex << c << dec << " ";checkCRC(nCheck, c); // CRC校验EtherType = c;// 输出类型字段的低8位c = file.get();cout.width(2);cout<< hex << c;checkCRC(nCheck,c); // CRC校验EtherType <<= 8; // 转换成主机格式EtherType |= c;// 定位下一个帧,以确定当前帧的结束位置while ( bParseCont ){for (int i = 0; i < 7; i++) //找下一个连续的7个0xaa {if (file.tellg() >= nFileEnd) //到文件末尾,退出循环 {bParseCont = false;break;}// 看当前字符是不是0xaa,如果不是,则重新寻找7个连续的0xaaif (file.get() != 0xaa){i = -1;}}// 如果直到文件结束仍没找到上述比特串,将终止主控循环的标记bParseCont置为truebParseCont = bParseCont && (file.tellg() < nFileEnd);// 判断7个连续的0xaa之后是否为0xabif (bParseCont && file.get() == 0xab){break;}}// 计算数据字段的长度nCurrDataLength =bParseCont ? // 是否到达文件末尾(file.tellg() - 8 - 1 - nCurrDataOffset) : // 没到文件末尾:下一帧头位置- 前导码和定界符长度 - CRC校验码长度 - 数据字段起始位置(file.tellg() - 1 - nCurrDataOffset); // 已到达文件末尾:文件末尾位置- CRC校验码长度 - 数据字段起始位置// 以文本格式数据字段,并校验cout << endl << "数据字段:\t";unsigned char* pData = new unsigned char[nCurrDataLength]; // 创建缓冲区 file.seekg(bParseCont ? (-8 - 1 -nCurrDataLength) : ( -1 - nCurrDataLength), ios::cur);file.read(pData, nCurrDataLength); // 读入数据字段int nCount = 50; // 每行的基本字符数量for (i = 0; i < nCurrDataLength; i++) // 输出数据字段文本{nCount--;cout << pData[i]; // 字符输出checkCRC(nCheck, (int)pData[i]); // CRC校验if ( nCount < 0) // 换行处理{// 将行尾的单词写完整if ( pData[i] == ' ' ){cout << endl << "\t\t";nCount = 50;}// 处理过长的行尾单词:换行并使用连字符if ( nCount < -10){cout<< "-" << endl << "\t\t";nCount = 50;}}}delete[] pData; //释放缓冲区空间// 输出CRC校验码,如果CRC校验有误,则输出正确的CRC校验码cout << endl <<"CRC校验";c = file.get(); // 读入CRC校验码int nTmpCRC = nCheck;checkCRC(nCheck, c); // 最后一步校验if ((nCheck & 0xff) == 0) // CRC校验无误{cout.width(2);cout<<"(正确):\t"<< hex << c;}else // CRC校验有误{cout.width(2);cout<< "(错误):\t" << hex << c;checkCRC(nTmpCRC, 0); // 计算正确的CRC校验码cout<< "\t应为:" << hex << (nTmpCRC & 0xff);bAccept = false; // 将帧的接收标记置为false}// 如果数据字段长度不足46字节或数据字段长度超过1500字节,则将帧的接收标记置为falseif (nCurrDataLength < 46 || nCurrDataLength > 1500 ){bAccept = false;}// 输出帧的接收状态cout<< endl << "状态:\t\t" << (bAccept ? "Accept" : "Discard") << endl <<endl; nSN++; // 帧序号加1nCurrDataOffset = file.tellg() + 22; // 将数据字段偏移量更新为下一帧的帧头结束位置}// 关闭输入文件file.close();}五、帧封装方法的相关扩展除了上面介绍的方法之外,还有其它一些算法可以完成CRC校验。