网络程序设计
一、实验目的
1、写一个程序来模拟网桥功能。
2、编写一个计算机程序用来计算一个文件的16位效验和。
二、实验内容及要求
1、模拟实现网桥的转发功能,以从文件中读取帧模拟网桥从网络中收到一帧,即从两个文件中读入一系列帧,从第一个文件中读入一帧然后从第二个文件中再读入一帧,如此下去。对每一帧,显示网桥是否会转发,及显示转发表内容。
要求:
Windows或Linux环境下运行,程序应在单机上运行。
2、编写一个计算机程序用来计算一个文件的16位效验和。最快速的方法是用一个32位的整数来存放这个和。记住要处理进位(例如,超过16位的那些位),把它们加到效验和中。
要求:
1)以命令行形式运行:check_sum infile
其中check_sum为程序名,infile为输入数据文件名。
2)输出:数据文件的效验和
三、实验分析
1、用程序模拟网桥功能,可以假定用两个文件分别代表两个网段上的网络帧数据。而两个文件中的数据应具有帧的特征,即有目的地址,源地址和帧内数据。程序交替读入帧的数据,就相当于网桥从网段中得到帧数据。
对于网桥来说,能否转发帧在于把接收到的帧与网桥中的转发表相比较。判断目的地址后才决定是否转发。由此可见转发的关键在于构造转发表。这里转发表可通过动态生成。
2、参见RFC1071 - Computing the Internet checksum
原理:把要发送的数据看成16比特的二进制整数序列,并计算他们的和。若数据字节长度为奇数,则在数据尾部补一个字节的0以凑成偶数。例子:16位效验和计算,下图表明一个小的字符串的16位效验和的计算。
为了计算效验和,发送计算机把每对字符当成16位整数处理并计算效验和。如果效验和大于16位,那么把进位一起加到最后的效验和中。
四、实验程序流程图
1、模拟网桥
模拟网桥程序流程图
2、检验和
检验和程序流程图
五、实验代码
1、Bridge.cpp /*模拟网桥*/
#include <iostream>
#include <fstream>
using namespace std;
#define MAX 10 /*转发表最大项数*/
#define null ' ' /*转发表项为空*/
//定义帧类
class frame
{
char source; /*帧的源MAC地址*/
char dest; /*帧的目的MAC地址*/
public:
//设置和获取帧信息
void SetSource(char source) /*设置帧的源MAC地址*/
{
this->source=source;
}
void SetDest(char dest) /*设置帧的目的MAC地址*/
{
this->dest=dest;
}
char GetSource() /*获取帧的源MAC地址*/
{
return source;
}
char GetDest() /*获取帧的目的MAC地址*/
{
return dest;
}
};
//定义转发表类
class tranTable
{
char dest[MAX]={ null,null,null,null,null,null,null,null,null,null }; /*收到帧的源地址作为下一次帧查表转发的目的地址,初始化为空表*/
int intface[MAX]; /*收到此帧的接口*/
public:
//设置转发表项
void SetDest(int i,char source) /*设置转发表项的目的地址*/
{
this->dest[i]=source;
}
void SetIntface(int i,int intface) /*设置转发表项目的地址的接口*/
{
this->intface[i]=intface;
}
//获取转发表项
char GetDest(int i) /*获取转发表项的目的地址*/
{
return dest[i];
}
char GetIntface(int i) /*获取转发表项的目的地址的接口*/
{
return intface[i];
}
//转发表的其它操作
void updateTable(char source,int intface) /*更新转发表*/
{
int i;
for (i = 0; this->dest[i] != null; i++); /*查找转发表的空项*/
SetDest(i, source); /*添加转发表项*/
SetIntface(i, intface);
cout<<"2.转发表中无主机"<<source-'A'+1<<"的MAC地址与转发接口信息,"<<"将主机"<<source-'A'+1<<"的MAC地址和转发接口信息添加到转发表。"<<endl;
cout<<"3.添加项("<<"MAC地址:"<<source<<" "<<"接口:"<<intface<<")"<<endl;
}
void searchTable(char source,char dest,int intface) /*查找转发表*/
{
int flag = 0; /*是否需要更新的标志*/
for(int i=0;this->dest[i]!=null;i++)
{
if (this->dest[i] == source) //转发表中没有和帧的源地址匹配的项目
{
cout << "2.无需更新转发表!" << endl;
flag = 1; /*无需更新转发表*/
break;
}
}
if (flag == 0)
{
updateTable(source, intface); //更新转发表
cout << "4.已更新转发表!" << endl;
}
flag = 0; /*需要更新转发表*/
for(int i=0;this->dest[i]!=null;i++)
{
if (this->dest[i] == dest&&this->intface[i] == intface)
{
cout << "5.转发表中给出的接口和该帧进入网桥的接口相同,丢弃此帧,不转发" << endl<<endl;
flag = 1;
break;
}
if(this->dest[i] == dest&&this->intface[i] != intface)
{
cout << "3.选择网桥接口:接口" << this->intface[i] << "进行转发" << endl<<endl;
flag = 1;
break;
}
}
if (flag==0) //转发表没有和目标地址相匹配的项
{
cout << "5.转发表没有和目标地址相匹配的项,选择非进入网桥的接口:接口" << 1 - intface << "进行转发" << endl<<endl;
}
}
};
int main()
{
frame data; /*定义数据帧*/
tranTable trantable1; /*定义转发表*/
fstream f1("d:\\LAN1.txt",ios::out),f2("d:\\LAN2.txt",ios::out); /*创建文件流对象f1,设置流为输出流,流的终点为指定路径文本文件*/
//写入数据,A、B、C、D分别代表主机1、主机2、主机3、主机4的MAC地址,“AB”A表示源MAC地址,B表示目的MAC地址
f1 << "AB BA AC AD BC BD"; /*向文件流写入数据*/
f2 << "CD DC CA DA CB DB";
f1.close(); /*关闭文件流f1*/
f2.close(); /*关闭文件流f2*/
f1.open("d:\\LAN1.txt",ios::in); /*打开以输入流方式的文件流f1,流的起点为指定路径文本文件*/
f2.open("d:\\LAN2.txt",ios::in);
cout<<"本实验有LAN1、LAN2两个网段和一个网桥,其中主机1、主机2在LAN1网段,主机3、主机4在LAN2网段。"<<endl<<endl;
while(f1.peek()!=EOF&&f2.peek()!=EOF) /*读取到文件尾*/
{//交替读取文件
char source,dest;
f1>>source>>dest; /*从文件流中读取一个帧*/
cout <<"1.主机"<< source-'A'+1 <<"发送帧到主机"<<dest-'A'+1<<endl ;
data.SetSource(source);
data.SetDest(dest);
trantable1.searchTable(data.GetSource(),data.GetDest(),0); /*查找转发表*/
f2>>source>>dest; /*从文件流中读取一个帧*/
cout <<"1.主机"<< source-'A'+1 <<"发送帧到主机"<<dest-'A'+1<<endl ;
data.SetSource(source);
data.SetDest(dest);
trantable1.searchTable(data.GetSource(),data.GetDest(),1); /*查找转发表*/
}
f1.close(); /*关闭文件流f1*/
f2.close(); /*关闭文件流f2*/
}
2、check_sum.cpp /*检验和*/
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc,char* argv[]) /*定义命令行参数*/
{
char fbuf[128]; /*缓存文件*/
int fbuflen=0; /*缓存文件大小*/
int checksum=0; /*检验和初始化为0*/
fstream infile("infile",ios::out); /*定义输出文件流*/
infile<<"Hello world."; /*向文件流中写入数据*/
infile.close();
if(argc==2) /*命令行的参数个数为2*/
{
infile.open(argv[1],ios::in); /*读取命令行第二个参数命令的文件*/
if(!infile) /*读取文件流失败*/
{
cout<<"读取文件失败!"<<endl;
return 0;
}
infile.get(fbuf,256); /*将文件流的数据保存到数组中*/
for(int i=0;fbuf[i]!='\0';i++) /*计算字符个数*/
{
fbuflen++;
}
cout<<"读取的文件内容:"<<fbuf<<endl;
if(fbuflen%2!=0) /*数据为奇数个时,在最末尾补一个字节的0*/
fbuf[fbuflen++]='0';
else
{
short bit_16[fbuflen/2]; /*16位整数的个数*/
cout<<"文件内容对应的整数序列:";
for(int i=0;i<(fbuflen/2);i++)
{
bit_16[i]=(fbuf[i*2]<<8)|fbuf[i*2+1]; /*每两个字符凑成16位整数*/
cout<<hex<<bit_16[i]<<' ';
checksum+=bit_16[i]; /*计算检验和*/
if(checksum>>16) /*进位*/
checksum=(checksum&0xffff)+(checksum>>16);
}
}
cout<<endl<<hex<<"校验和:"<<checksum<<endl;
}
else /*命令行的参数个数不为2*/
{
cout<<"请以命令行形式运行:check_sum infile"<<endl;
}
return 0;
}
六、实验过程
1、模拟网桥
1)编辑并编译Bridge.cpp文件,生成可执行文件
2)运行结果
2、检验和
1)编辑并编译check_sum.cpp文件,生成可执行文件
2)运行check_sum命令,提示输入check_sum infile命令运行
3)运行check_sum infill命令,输入错误的文件名,显示读取文件失败
4)运行check_sum infill命令,正确输入check_sum infile命令,显示正确的结果
七、实验总结
1、模拟网桥的基本思路是,两个文件模拟两个网段LAN1、LAN2,文件的交替读取表示网段LAN1的主机发送数据帧到网段LAN2的主机。其中LAN1的主机发送数据帧到LAN1主机、LAN1主机发送数据帧到LAN2主机和LAN2主机发送数据帧到LAN1主机都要通过网桥转发表的存储转发。
当主机进行通信时,数据帧首先发送到网桥。网桥提取到数据帧的信息后,会在转发表中查找是否有源主机的MAC地址,如有,无需更新转发表。如果没有,需要把该源主机的MAC地址和数据帧进入网桥的接口信息存储到转发表。网桥处理完数据帧的源MAC地址信息后,接着提取数据帧的目的MAC地址和转发表的表项进行匹配,根据匹配的信息选择是否转发。如无匹配信息,选择将数据帧从非进入接口进行转发。
当网桥的转发表建立好时,相同网段的主机通信时,不转发数据帧,即丢弃此帧。不同网段的主机通信,转发数据帧。
2、校验和的实质是将数据分成多个16位整数,并进行求和。校验和求和过程中有几点是需要注意的:首先是必须检查16位整数的个数是否为偶数,如不是偶数个,需要在数据的末尾补上一个字节的0;其次是校验和的位数超过16位时,需要对校验和进行进位操作。