资源描述
计算机通信网络实验
数据链路层协议的设计与实现
学院:
班级:
学号:
姓名:
2012年11月11日
一、 实验目的
计算机网络的数据链路层协议保证通信双方在有差错的通信线路上进行无差错的数据传输,是计算机网络各层协议中通信控制功能最典型的一种协议。
本实验实现一个数据链路层协议的数据传送部分,目的在于更好地理解基本数据链路层协议的基本工作原理,掌握计算机网络协议的基本实现技术。
二、 实验内容
使用C 语言实现下面数据链路层协议:
1. 分析和实现一个理想的链路层协议
2. 对于前面实现的协议进行扩充,实现它的第一次改进,如何防止发方过快淹没收方。
3. 对上一步再假设在不可靠的的链路上进行通信。
三、 实验步骤
1. 熟悉数据链路层协议的功能;
2. 编写数据链路层协议的实现程序;
3. 调试并运行自己编写的协议实现程序;
4. 了解协议的工作轨迹,如出现异常情况,在实验报告中写出原因分析;
5. 保留你实现的数据链路层协议,以备教师检查。
四、 实验过程
1、程序功能及设计思路
功能概述:
用客户端/服务器模式代表A站、B站。先由客户端输入服务器IP地址,发送SYN同步帧,告诉服务器准备接受。客户端输入数据后,会进行CRC编码,再发送数据帧;服务器收到后,先进行校验,数据正确则发送ACK帧,客户端则发送下一帧数据;否则服务器发送NAK帧,客户端重新发送该数据。
CRC校验:
1) 将收到的字符转为int型(32位),并将其二进制码左移16位,存于data;
2) 进行C(D)=Remainder[(S(D)∙D^L)/g(D) ],即CRC校验,得到校验位。
3) 将校验位加在信息元后,组成24位的码字,存于要发送的数据帧dframe。
停等式ARQ协议:
Client:
1) 置SN=0;
2) 收到数据,将SN分配给该数据,如果没有收到,则等待;
3) 存于要发送的数据帧中,发送给server;
4) 如果从server收到确认帧,且RN>SN,则SN加1(模2),返回2;如果收到NAK或RN=SN,则返回3,重传数据。
Server:
1) 置RN=0;
2) 从client收到一个SN=RN的帧,进行CRC校验检查,无错后输出,并置RN加1、发送ACK帧;否则发送NAK帧,请求重发。
2、C语言程序代码:
客户端Client:
//*********************** client.c *****************************
#include<winsock.h> //WINSOCK API的头文件,需要包含在项目中
#pragma comment(lib,"ws2_32.lib") //WINSOCK API连接库文件
#include<stdio.h>
#include<string.h>
int err;
SOCKET sock; //用于服务器监听的Socket
SOCKADDR_IN addrSrv; //服务端地址
unsigned char sendBuf[100]; //发送缓存
char serverIp[20]; //客户端ip地址
int socklen=sizeof(SOCKADDR_IN); //Socket的地址值的长度
int cf_len=sizeof(struct sockaddr);
struct dataFrame //数据帧
{
int seq; //分段消息的序号
int SN; //发送序号
unsigned int data[100];
int msglen; //字符长度,采用长度计数的组帧技术
};
struct conFrame //控制帧
{
int RN; //接收序号
char type[3];
//表明帧的类型:SYN同步、EOT送毕、ACK确认应答、NCK否定应答
};
struct dataFrame dframe;
struct conFrame cframe;
//************************ 初始化******************************
void initialization()
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD( 1, 1 ); //WinSocket1.1版本
err = WSAStartup( wVersionRequested, &wsaData );
//wsaData用来存储系统传回的关于WinSocket的资料
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ){
WSACleanup( );
}
return;
}
//************************ 计算CRC-16******************************
//基于32位系统,int型长度为4字节,CRC-16的生成多项式为g(D)=D^16+D^15+D^2+1
void caculate_crc16(unsigned char*msg,int lenth,unsigned int *crc)
{
unsigned int data=0;
int i,j;
// char s[32]; //用于测试时显示二进制码
for(i=0;i<lenth;i++,crc++,msg++){
data=(int)*msg;
// itoa(data,s,2); //把整数转为二进制码
// printf("第%d字的二进制码:%s\n",i+1,s); //test
*crc=data<<16;
for(j=0;j<16;j++)
{
if((data&0x8000))
{
data=(data<<1)&0xffff;
data=data^0x8005;
}
else data=(data<<1)&0xffff;
}
*crc=*crc|data; //把校验位放在信息元后面,存在一个int变量中
// itoa(*crc,s,2);
// printf("加上CRC校验位后的二进制码:%s\n",s); //test
}
}
//************************发送数据帧******************************
void SendFrame(){
//建立socket,SOCK_DGRAM为使用不连续不可靠的数据包连接
sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock==-1){
printf("Building a socket failed.\n");
return;
}
printf("input server's IP:");
scanf("%s",serverIp); //输入服务器ip
addrSrv.sin_addr.S_un.S_addr=inet_addr(serverIp); //设置服务器地址
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000); // 设置服务器端口号
strcpy(cframe.type,"SYN");
cframe.RN=0;
//发送同步消息
sendto(sock,(char*)&cframe,sizeof(cframe)+1,0,(SOCKADDR*)&addrSrv,socklen);
printf("----------------------------------------------\n");
dframe.seq=0;
while(1)
{
memset(sendBuf,0,sizeof(sendBuf)); //清空缓冲区
printf("Input the message:"); //输入message,若输入exit则停止发送
scanf("%s",sendBuf);
if(strcmp(sendBuf,"exit")==0){
//当客户端要断开连接时,给服务器发送EOT送毕控制帧
strcpy(cframe.type,"EOT");
//发送EOT消息
sendto(sock,(char*)&cframe,sizeof(cframe)+1,0,(SOCKADDR *)&addrSrv,socklen);
printf("------------------close socket!-----------------\n");
break;
}
dframe.seq++;
dframe.SN=dframe.seq%2;
dframe.msglen=strlen(sendBuf);
caculate_crc16(sendBuf,dframe.msglen,dframe.data);
//发送消息
sendto(sock,(char*)&dframe,sizeof(struct dataFrame),0,(SOCKADDR *)&addrSrv,socklen);
while(1)
{
recvfrom(sock,(char*)&cframe,sizeof(struct conFrame),0,(SOCKADDR *)&addrSrv,&cf_len); //接受来自服务器的应答帧
if(strcmp(cframe.type, "ACK")==0) //如果收到ACK应答指令,则发送下一个message
{
if(cframe.RN!=dframe.SN){
printf("--------message is delivered successfully.----------\n");
break;
}
}else if((strcmp(cframe.type, "NAK")==0)|(cframe.RN==dframe.SN))
{
printf("--------message redeliverring.----------\n");
sendto(sock,(char*)&dframe,sizeof(struct dataFrame),0,(SOCKADDR *)&addrSrv,socklen); //重发该消息
}
}
}
closesocket(sock); //关闭连接
return;
}
void main(){
initialization(); //初始化阶段,若返回值err=0,则表示初始化成功
if(err){
printf("Initialization falied.\n");
exit(0);
}
SendFrame();
WSACleanup();
}
//********************** end of program ************************
服务器Server:
//*********************** server.c *****************************
#include<winsock.h> //WINSOCK API的头文件,需要包含在项目中
#pragma comment(lib,"ws2_32.lib") //WINSOCK API连接库文件
#include<stdio.h>
#include<string.h>
int err;
SOCKET sock; //用于服务器监听的Socket
SOCKADDR_IN addrSrv; //服务端地址
SOCKADDR_IN addrClient; //客户端地址
unsigned char recvBuf[100]; //接受缓存
int cf_len=sizeof(struct sockaddr); //实际存储在recvBuf的地址的长度
int socklen=sizeof(SOCKADDR_IN); //Socket的地址值的长度
struct dataFrame //数据帧
{
int seq; //分段消息的序号
int SN; //发送序号
unsigned int data[100];
int msglen; //字符长度,采用长度计数的组帧技术
};
struct conFrame //控制帧
{
int RN; //接收序号
char type[3];
//表明帧的类型:SYN同步、EOT送毕、ACK确认应答、NCK否定应答
};
struct dataFrame dframe;
struct conFrame cframe;
//************************ 初始化******************************
void initialization()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 ); //WinSocket1.1版本
err = WSAStartup( wVersionRequested, &wsaData );
//wsaData用来存储系统传回的关于WinSocket的资料
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ){
WSACleanup( );
}
return;
}
//************************ 绑定端口 ******************************
void bindport()
{
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
//设置服务器地址,INADDR_ANY表示使用自己的IP地址
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000); //设定端口为6000
err=bind(sock,(LPSOCKADDR)&addrSrv,sizeof(SOCKADDR));
return;
}
//********************** CRC校验 **************************
void checkout(unsigned int *pdata,int lenth)
{
int i,j;
unsigned int temp;
unsigned int data;
unsigned char *msg=recvBuf; //*msg指向recvBuf的首地址
// unsigned char s[32]; //用于测试时显示二进制码
for(i=0;i<lenth;i++,msg++)
{
data=*pdata;
temp=(data&0xff0000)>>16; //将校验位清零,使信息位移到低8位
*msg=temp;
// itoa(data,s,2); //把整数转为二进制码 //test
// printf("收到的二进制码:%s\n",s); //test
for(j=0;j<16;j++) //有16位校验元,故循环操作16次,使得到Remainder[R(x)/g(x)]
{
if(data&0x800000)
{
data=(data<<1)&0xffffff;
data=data^0x800500;
}
else data=(data<<1)&0xffffff;
}
if(data==0){
strcpy(cframe.type,"ACK");
//若Remainder[R(x)/g(x)]=0,表示接收序列无误
}
else {
strcpy(cframe.type,"NAK");
//若Remainder[R(x)/g(x)]!=0,表示接收序列有误
return;
}
pdata++;
}
return ;
}
//********************** 发送及接受消息 **************************
void RecvFrame()
{
printf("--------------------server waitting.--------------------\n");
while(1)
{
recvfrom(sock,(char*)&cframe,sizeof(struct conFrame),0,(SOCKADDR *)&addrClient,&cf_len); //接受来自客户端的同步帧
if(strcmp(cframe.type,"SYN")==0){
printf("---------connect with %s.-------\n",inet_ntoa(addrClient.sin_addr));
break;
}
}
while(1)
{
cf_len=sizeof(struct sockaddr);
memset(recvBuf,0,sizeof(recvBuf)); //清空接收缓冲区
err=recvfrom(sock,(char*)&dframe,sizeof(struct dataFrame),0,(SOCKADDR *)&addrClient,&cf_len); //接受来自客户端的数据帧
if(err==9){
//当客户端关闭socket时,发送过来的数据会使err=9,则服务器也关闭socket
printf("---------client close the socket!-------\n");
break;
}
checkout(dframe.data,dframe.msglen);
//调用checkout()函数,CRC检验接收消息是否正确
if(strcmp(cframe.type,"ACK")==0)
{
printf("client:%s\n",recvBuf);
printf("---------message is correct!-------\n");
cframe.RN=(dframe.SN+1)%2;
}
else if(strcmp(cframe.type,"NAK")==0)
{
cframe.RN=dframe.SN;
printf("----------message is wrong!-------\n");
}
sendto(sock,(char*)&cframe,sizeof(cframe),0,(SOCKADDR *)&addrClient,socklen);
//发送ACK帧或NAK帧
}
printf("--------------------The end!------------------\n");
return;
}
void main()
{
initialization(); //初始化阶段,若返回值err=0,则表示初始化成功
if(err!=0){
printf("Intialization failed.\n");
return;
}
sock=socket(AF_INET,SOCK_DGRAM,0);
//建立socket,SOCK_DGRAM为使用不连续不可靠的数据包连接
if(sock==-1){
printf("Building a socket failed.\n");
return;
}
bindport(); //绑定端口
if(err!=0){
printf("Binding a socket failed.\n");
return;
}
RecvFrame(); //接受客户端发送数据
closesocket(sock); //关闭连接
WSACleanup();
}
//********************** end of program ************************
3、实验结果
测试1:
在没有传输错误情况下,在dos界面显示的传递内容的二进制码和CRC校验码
Client: 222.25.162.196,发送数据“yl”,显示“message is delivered successfully”后关闭连接。
Server: 222.25.162.196,收到数据“yl”,与上图对比,码字正确,则正确输出数据。
测试2:
在传输出错情况下,在dos界面显示传递内容的二进制码和CRC校验码
Client:222.25.162.196,发送数据“yl”,传输出错收到NAK后重传数据。
Server:222.25.162.196,与上图的二进制码对比发现,第2个码字最后1一位出错,
程序显示“message is wrong”,然后等待客户端重传。
通信测试:
在dos界面进行通信,但隐藏了二进制码、校验码,实现的是数据的透明传输。
Client:222.25.162.5
Server:222.25.162.196
分析:从上面两图可以看出,服务端和客户端可以实现通畅且准确无误的通信,基本能够实现预期功能。
五、 实验心得
通过本次实验,对数据链路层中数据帧的传输和CRC校验有了更深的了解,不仅仅是停留在书本的理论上了,而且自己对网络编程也有了更大的兴趣。
展开阅读全文