1、数据链路层协议书的设计实现分析计算机通信网络实验 数据链路层协议的设计与实现 学院: 班级: 学号: 姓名: 2012年11月11日一、 实验目的计算机网络的数据链路层协议保证通信双方在有差错的通信线路上进行无差错的数据传输,是计算机网络各层协议中通信控制功能最典型的一种协议。本实验实现一个数据链路层协议的数据传送部分,目的在于更好地理解基本数据链路层协议的基本工作原理,掌握计算机网络协议的基本实现技术。二、 实验内容使用C 语言实现下面数据链路层协议:1. 分析和实现一个理想的链路层协议2. 对于前面实现的协议进行扩充,实现它的第一次改进,如何防止发方过快淹没收方。3. 对上一步再假设在不可
2、靠的的链路上进行通信。三、 实验步骤1. 熟悉数据链路层协议的功能;2. 编写数据链路层协议的实现程序;3. 调试并运行自己编写的协议实现程序;4. 了解协议的工作轨迹,如出现异常情况,在实验报告中写出原因分析;5. 保留你实现的数据链路层协议,以备教师检查。四、 实验过程1、程序功能及设计思路功能概述:用客户端/服务器模式代表A站、B站。先由客户端输入服务器IP地址,发送SYN同步帧,告诉服务器准备接受。客户端输入数据后,会进行CRC编码,再发送数据帧;服务器收到后,先进行校验,数据正确则发送ACK帧,客户端则发送下一帧数据;否则服务器发送NAK帧,客户端重新发送该数据。CRC校验:1) 将
3、收到的字符转为int型(32位),并将其二进制码左移16位,存于data;2) 进行C(D)=Remainder(S(D)DL)/g(D) ,即CRC校验,得到校验位。3) 将校验位加在信息元后,组成24位的码字,存于要发送的数据帧dframe。停等式ARQ协议:Client: 1) 置SN=0;2) 收到数据,将SN分配给该数据,如果没有收到,则等待;3) 存于要发送的数据帧中,发送给server;4) 如果从server收到确认帧,且RNSN,则SN加1(模2),返回2;如果收到NAK或RN=SN,则返回3,重传数据。Server:1) 置RN=0;2) 从client收到一个SN=RN的
4、帧,进行CRC校验检查,无错后输出,并置RN加1、发送ACK帧;否则发送NAK帧,请求重发。 2、C语言程序代码:客户端Client:/* client.c *#include /WINSOCK API的头文件,需要包含在项目中#pragma comment(lib,ws2_32.lib) /WINSOCK API连接库文件#include#includeint err;SOCKET sock; /用于服务器监听的SocketSOCKADDR_IN addrSrv; /服务端地址unsigned char sendBuf100; /发送缓存char serverIp20; /客户端ip地址in
5、t socklen=sizeof(SOCKADDR_IN); /Socket的地址值的长度int cf_len=sizeof(struct sockaddr);struct dataFrame /数据帧 int seq; /分段消息的序号 int SN; /发送序号 unsigned int data100; int msglen; /字符长度,采用长度计数的组帧技术;struct conFrame /控制帧 int RN; /接收序号 char type3; /表明帧的类型:SYN同步、EOT送毕、ACK确认应答、NCK否定应答;struct dataFrame dframe;struct
6、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 ) WSACle
7、anup( ); return; /* 计算CRC-16*/基于32位系统,int型长度为4字节,CRC-16的生成多项式为g(D)=D16+D15+D2+1void caculate_crc16(unsigned char*msg,int lenth,unsigned int *crc) unsigned int data=0; int i,j; / char s32; /用于测试时显示二进制码 for(i=0;ilenth;i+,crc+,msg+) data=(int)*msg; / itoa(data,s,2); /把整数转为二进制码 / printf(第%d字的二进制码:%sn,i+
8、1,s); /test *crc=data16; for(j=0;j16;j+) if(data&0x8000) data=(data1)&0xffff; data=data0x8005; else data=(data1)&0xffff; *crc=*crc|data; /把校验位放在信息元后面,存在一个int变量中 / itoa(*crc,s,2); / printf(加上CRC校验位后的二进制码:%sn,s); /test /*发送数据帧*void SendFrame()/建立socket,SOCK_DGRAM为使用不连续不可靠的数据包连接 sock=socket(AF_INET,SOC
9、K_DGRAM,0); if(sock=-1) printf(Building a socket failed.n); return; printf(input servers 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;/发送同步消息 sen
10、dto(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);/发
11、送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,(SOC
12、KADDR *)&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,
13、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); SendF
14、rame(); WSACleanup();/* end of program *服务器Server:/* server.c *#include /WINSOCK API的头文件,需要包含在项目中#pragma comment(lib,ws2_32.lib) /WINSOCK API连接库文件#include#includeint err;SOCKET sock; /用于服务器监听的SocketSOCKADDR_IN addrSrv; /服务端地址SOCKADDR_IN addrClient; /客户端地址unsigned char recvBuf100; /接受缓存int cf_len=siz
15、eof(struct sockaddr); /实际存储在recvBuf的地址的长度int socklen=sizeof(SOCKADDR_IN); /Socket的地址值的长度struct dataFrame /数据帧 int seq; /分段消息的序号 int SN; /发送序号 unsigned int data100; int msglen; /字符长度,采用长度计数的组帧技术;struct conFrame /控制帧 int RN; /接收序号 char type3; /表明帧的类型:SYN同步、EOT送毕、ACK确认应答、NCK否定应答;struct dataFrame dframe
16、;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.wVersi
17、on ) != 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
18、 int *pdata,int lenth) int i,j; unsigned int temp; unsigned int data; unsigned char *msg=recvBuf; /*msg指向recvBuf的首地址 / unsigned char s32; /用于测试时显示二进制码 for(i=0;i16; /将校验位清零,使信息位移到低8位 *msg=temp; / itoa(data,s,2); /把整数转为二进制码 /test / printf(收到的二进制码:%sn,s); /test for(j=0;j16;j+) /有16位校验元,故循环操作16次,使得到Rema
19、inderR(x)/g(x) if(data&0x800000) data=(data1)&0xffffff; data=data0x800500; else data=(data1)&0xffffff; if(data=0) strcpy(cframe.type,ACK); /若RemainderR(x)/g(x)=0,表示接收序列无误 else strcpy(cframe.type,NAK); /若RemainderR(x)/g(x)!=0,表示接收序列有误 return; pdata+; return ;/* 发送及接受消息 *void RecvFrame() printf(-serve
20、r 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); /清
21、空接收缓冲区 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
22、,ACK)=0) printf(client:%sn,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
23、;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); r
24、eturn; 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校验码Cl
25、ient:222.25.162.196,发送数据“yl”,传输出错收到NAK后重传数据。Server:222.25.162.196,与上图的二进制码对比发现,第2个码字最后1一位出错,程序显示“message is wrong”,然后等待客户端重传。通信测试:在dos界面进行通信,但隐藏了二进制码、校验码,实现的是数据的透明传输。Client:222.25.162.5Server:222.25.162.196 分析:从上面两图可以看出,服务端和客户端可以实现通畅且准确无误的通信,基本能够实现预期功能。五、 实验心得通过本次实验,对数据链路层中数据帧的传输和CRC校验有了更深的了解,不仅仅是停留在书本的理论上了,而且自己对网络编程也有了更大的兴趣。