停止等待协议实验报告.docx
《停止等待协议实验报告.docx》由会员分享,可在线阅读,更多相关《停止等待协议实验报告.docx(18页珍藏版)》请在冰豆网上搜索。
停止等待协议实验报告
实验停顿等待协议分析与协议模拟实现
一、实验目的和任务
1.掌握停顿等待协议的原理及分析过程包括使用状态转移图进展协议的分析。
2.在计算机上编程模拟停顿等待协议的工作过程并实现文件的端到端传输。
3.能够在文件的传输过程中表现出协议运行所遇到的各种状况,如丢包,过失控制等
二、分析与设计
1.设计任务分析:
停顿等待协议是数据链路层的几个协议中最简单的协议,是具有最简单流量控制的数据链路层协议,是数据链路层各种协议的根底。
实验是基于winsock编程,是visualC++6.0win32控制台运用程序实现的。
它采用客户机/效劳器〔C/S〕模型,即发送数据的一端为客户端,接收数据的一端为效劳器端。
停顿等待协议就是通过双方的收发数据而到达相互通信的目的。
本实验通过编程模拟实现停顿等待协议,随机的发送文件,通过效劳器的的承受结果和客户端的承受结果显示理解停顿等待协议的原理,掌握其应用。
2. 协议分析
假定1:
链路是理想的传输信道,所传送的任何数据既不会出过失也不会丧失。
假定2:
不管发方以多快的速率发送数据,收方总是来得及收下,并及时上交主机。
这个假定就相当于认为:
接收端向主机交付数据的速率永远不会低于发送端发送数据的速率。
如果存在这样的传输信道,数据链路层协议也是不需要的。
信道不会出错,而且接收方缓存的容量为无限大而永远不会溢出;或接收速率与发送速率绝对准确相等。
在上述两个假定的情况下,数据链路层当然就不需要任何协议就可以保证数据传输的正确。
这就是说,传输数据的信道是不可靠的〔即不能保证所传的数据不产生过失〕,并且还需要对数据的发送端进展流量控制。
现在不能保证接收端向主机交付数据的速率永远不低于发送端发送数据的速率。
由收方控制发方的数据流收方每承受到发方一帧后,回复确认帧,让发方继续发送下一帧,并且收方将数据帧交给上层软件识别,出现错误就将帧丢掉.在大多数协议中,流量控制是一组过程,这组过程是用来告诉发送方在等待接收方的应答信号之前最多可以传送多少数据。
流量控制有两个要点:
〔1〕数据流不能使接收方过载。
任何接收设备都有一个处理输入数据的速率限制,并且存储输入数据的存储器容量也是有限的。
接收设备必须在到达这些限制之前通知发送设备并且请求发送设备发送较少的数据帧或是暂停一会儿。
在使用输入数据之前,需要对数据进展校验和处理,因此,每个接收设备都有一块存储器,叫做缓冲区,用于存放未来得及处理的数据帧。
如果缓冲区将满,接收方也必须能够通知发送方暂停传输,直到接收方又能接收数据。
〔2〕应答。
随着数据帧的到来,接收方对他们进展应答,可以每收到一帧给一个应答,也可以一次对假设干帧进展应答。
如果一个帧到达时已经被破坏,接收方发送一个否认应答帧〔NAK〕。
在数据链路层,过失控制主要指错误检测和重传方法。
在一个帧中出现任何一个错误,接收方就返回一个否认应答帧,出错的帧就被发送方重新传送。
这个过程被称作自动重复请求(ARQ)。
数据被重传的情况有三种:
帧破坏、帧丧失和应答帧丧失。
流量控制和过失控制是结合在一起实现的,共有两种实现流量控制和过失控制的技术:
停顿等待协议和滑动窗口协议。
可以用多种方法来表示一个有限状态机,对协议进展描述,以下只描述一种。
1〕混合描述方法
比拟实用的方法是合并一些状态,即考虑一些次要的细节。
例如,甲方的状态1和状态2,状态3和状态4都可以合并,乙的状态1和状态4,状态2和状态3也可进展合并。
这样可以用3个字符XYZ表示整个系统的状态,其中X=0或1,对应于甲方准备发[0]或[1]〔包括发完后等待ACK的状态〕;Y=0或1,对应于乙方期望收到[0]或[1];Z=0、l、A或-,对应于信道上传送的是[0]、[1]、ACK或出现了过失〔包括丧失〕。
这样,就可得出图3-24的有限状态机。
在弧线〔或直线〕旁边注明的数字为状态变迁的标号,其意义也注明在图3-24的右方。
假设系统一开场处在〔000〕状态。
这表示甲发完[0],乙期望收到[0],而信道上传送的也是[0]。
在无过失的情况下,系统的状态仅在4个状态中循环:
〔000〕→〔01A〕→〔111〕→〔10A〕→〔000〕→‥‥。
从理论上讲,应当共有2×2×4=16种不同的状态。
去掉没有意义的组合后,还剩下10种状态,而导致状态变迁的输人事件共有9种〔标号0~8〕。
这种有限状态机可帮助我们检查协议是否正确。
例如,检查一下乙方会不会连续将两个0号帧送交主机。
这相当于检查一下会不会出现这种情况,即在两次出现状态变迁1之间不出现状态变迁3。
仔细检查图3-24就可发现这种情况是不会发生的。
同样方法也可用来排除连续将两个1号帧送交主机的可能。
再检查一下会不会发生甲方连续改变状态2次〔如从0到1,再回到0〕而乙方的状态未改变。
这种情况相当于出现了未被发现的报文丧失。
可以看出,这种情况也是不存在的。
协议必须不出现死锁。
死锁的出现是因为存在着这样的一种状态子集,其特点是:
从这一子集变迁到子集外是不可能的,而在这一子集状态的变迁总是局限于子集的几个状态。
可以看出,如下图的自动机没有死锁现象。
3.设计方案论证
当收方收到一个正确的数据帧后,便会向发方发送一个确认帧ACK,表示发送的数据正确接收。
当发方收到确认帧后才能发送一个新的数据帧,这样就实现了接收方对发送方的流量控制。
由于通信线路质量各方面的影响,数据帧从发送方到接收方传输的过程中可能会出现过失。
为了保证数据的正确性和完整性,接收方在收到数据后,会用一定的方法对接收到的数据进展过失检验,所以接收方很容易检测出收到的数据帧是否出现过失。
当接收方发现收到的数据出现过失时,就会向发送方发送一个否认帧NAK,表示对方发送的数据错误。
发送方会根据接收方发来的信息做出相应的操作。
采用这样的有效的检错机制,数据链路层可以对上面的网络层提供了可靠的传输的效劳。
三、系统运行与验证
程序分两局部:
客户程序和效劳器程序。
工作过程是:
效劳器首先启动,它创立套接字之后等待客户的连接;客户启动后创立套接字,然后和效劳器建立连接;建立连接后,客户写入文件的路径,然后将文件发送到效劳器,效劳器要求写入保存的文件路径,收到到文件后,将接收到的文件保存到指定路径当中。
效劳器端运行图:
客户端运行图
成功发送文件后的
效劳器端
客户端
文件发送失败
客户端的响应
客户端向效劳器端发送文件请求ENQ,但是没有收到返回帧,客户端显示sendfilefailed,而filesendfailed说明文件经过规定次数重传后文件还是发送失败。
四、总结与体会
1.分组情况
润:
负责停顿等待协议模拟客户端程序的编写、调试。
毛凤阳:
负责停顿等待协议模拟效劳端程序的编写、调试。
黄晓明:
负责查阅相关资料,实验报告的撰写,编写头文件。
2.总结
通过本次实验及课上教师讲解,对停顿等待协议有了更深刻的了解。
并且通过C/S代码的编写运行,形象地看到客户/效劳器端的运作方式,对于C/S模型有了很深刻的印象以及进一步理解。
通过代码的编写,再一次熟悉Socket编程原理,掌握简单的套接字编程。
运行程序成功后,是在同一台电脑上进展C与S端的连接。
而且使用的是TCP协议,所以要模拟停顿等待协议发送丢包,超时等情况比拟困难。
仅仅实现了文件发送时等待应答信号超时的情况。
编程时遇到许多困难,从一个新手通过查阅相关的资料和以前的学习以及和同学之间的交流进步到逐步了解。
在设计过程中,组员之间相互促进,相互交流,共同进步。
发送端程序
//sender.cpp:
定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include
#include
#include
#include"../header/ARQ.h"
#include"../header/Exception.h"
//效劳器端口
#defineSERVER_PORT2280
//最大重传次数
#defineMAXRETRY8
//传送传时时间
#defineTIMEOUT3000
#pragmament(lib,"ws2_32.lib")//设置link时的lib库,参加ws2_32.lib到工程,此库文件与socket编程有关
//也可以在MFC过在project->settings->link中参加
//主要是获得Ws2_32.dll
usingnamespacestd;
SOCKETPrimaryUDP;//定义一个socket号
charServerIP[20];//定义数组保存效劳器的IP地址
charFilePath[MAX_PATH];//定义文件的路径存储数
//用作奇偶检校的序号
boolg_number=false;
//返回的控制字符
charg_bcc;
HANDLEm_hEvent;
voidInitWinSock()//初始化socket
{
WSADATAwsaData;//TheWSADATAstructureisusedtostoreWindowsSocketsinitializationinformation
//returnedbyacalltotheAfxSocketInitglobalfunction.
if(WSAStartup(MAKEWORD(2,2),&wsaData)!
=0)//TheWSAStartupfunctioninitiatesuseofWs2_32.dllbyaprocess.
//TheMAKEWORDmacrocreatesaWORDvaluebyconcatenatingthespecifiedvalues.
{
throwException("Windowssockets2.2startupunsuccessful");
}
else{
printf("Using%s(Status:
%s)\n",
wsaData.szDescription,wsaData.szSystemStatus);
printf("withAPIversions%d.%dto%d.%d\n\n",
LOBYTE(wsaData.wVersion),HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion),HIBYTE(wsaData.wHighVersion));
}
}
voidmksock(inttype)//创立socket号
{
PrimaryUDP=socket(AF_INET,type,0);//socket()函数创立一个socket号
if(PrimaryUDP<0)
{
throwException("createsocketerror");
}
}
voidBindSock()//绑定一个socket号和本地进程〔用地址和端口号描述〕
{
sockaddr_insin;//定义一个套接字地址
sin.sin_addr.S_un.S_addr=INADDR_ANY;//获取本地IP地址
sin.sin_family=AF_INET;//协议族TCP/IP
sin.sin_port=0;//系统随机获取端口号
if(bind(PrimaryUDP,(structsockaddr*)&sin,sizeof(sin))<0)//绑定
throwException("binderror");
}
boolASendto()
{
sockaddr_inremote;
remote.sin_addr.S_un.S_addr=inet_addr(ServerIP);
remote.sin_family=AF_INET;
remote.sin_port=htons(SERVER_PORT);
intfromlen=sizeof(remote);
//翻开文件
FILE*file;
if((file=fopen(FilePath,"rb"))==NULL)//"rb"说明文件只读并按二进制方式翻开
{
cout<returnfalse;
}
cout<<"fileopensucceed"<//设置文件指针位置
SetFilePointer(file,0,NULL,FILE_BEGIN);//使FILE_BEGIN指向文件开头
BSCbsc;
bsc.header=STX;bsc.tail=ETX;
//设置为有信号状态
SetEvent(m_hEvent);
//分段序号
boolnumber=false;
unsignedlongdwRead=-1;
boolsendplete=false;
while(!
sendplete)
{
//清空数据
memset(bsc.data,0,MAXBSCLENGTH);//把bsc.data前MAXBSCLENGTH字符用代替
//当前分块的奇偶序号
bsc.number=number;
//记录当前的分块序号
g_number=bsc.number;
if(dwRead==-1)//第一次应发送文件请求消息
{
//发送文件请求
bsc.bcc=ENQ;//询问
cout<<"ENQ";
char*filename=FilePath;
if((filename=strrchr(FilePath,'\\'))==NULL)//在FilePath中寻找\字符,返回指向最后一个\字符的指针
filename=FilePath;//获取文件名
else
++filename;
strcpy(bsc.data,filename);
dwRead=0;
}
else
{
if(!
feof(file))//如果没有检查到file的完毕符
{
bsc.bcc=SYN;//同步
cout<<"SYN";
inti=fread(bsc.data,sizeof(char),MAXBSCLENGTH,file);//把文件读入bsc.data,返回字节数
cout<<"read:
"<
"<dwRead+=i;//deRead等于读到的字节数
}
else
{
//发送完毕
bsc.bcc=EOT;//完毕
cout<<"EOT";
sendplete=true;
cout<<"sendplete.sendsize:
"<fclose(file);
}
}
//MAXRETRY为最大达重传次数
for(inti=0;i{
sendto(PrimaryUDP,(char*)&bsc,sizeof(bsc),0,(sockaddr*)&remote,fromlen);
ResetEvent(m_hEvent);//设置为无信号状态
DWORDreslut=WaitForSingleObject(m_hEvent,TIMEOUT);//等待信号的到来,如果在TIMEOUT时间信号不到来,
//线程不再等待,函数返回,如果想让线程一直等待,需设置该参数为INFINITE
if(reslut==WAIT_OBJECT_0)//说明事件是有信号状态返回
{
//收到应答消息,一种是ACK,一种是NAK
if(g_bcc==NAK)
{
if(i==MAXRETRY-1)
{
returnfalse;
}
//继续重传
continue;
}
else
{
//收到应答消息
cout<<"sendsucceed"<break;
}
}
elseif(i==MAXRETRY-1)//没有收到返回帧
{
cout<<"sendfilefailed"<returnfalse;
}
}
//开场发下一段数据
number=!
number;
}
returntrue;
}
DWORDWINAPIARecv(LPVOIDlpParam)
{
sockaddr_inremote;
intsinlen=sizeof(remote);
BSCbuffer;
intiread=0;
while(true)
{
iread=recvfrom(PrimaryUDP,(char*)&buffer,sizeof(buffer),0,(sockaddr*)&remote,&sinlen);
//处理ACK与NAK
if(iread==SOCKET_ERROR)
{
continue;
}
//与当前的分块序号进展比拟,看是不是当前块的应答
if(buffer.number!
=g_number)
{
continue;
}
if(buffer.bcc==ACK||buffer.bcc==NAK)
{
//保存返回的控制字符
g_bcc=buffer.bcc;
SetEvent(m_hEvent);//设置为有信号状态
}
}
return0;
}
int_tmain(intargc,_TCHAR*argv[])//main函数
{
InitWinSock();
mksock(SOCK_DGRAM);
BindSock();
cout<<"Pleaseinputreceiverip:
";
cin>>ServerIP;
cout<<"Pleaseinputthefilepath:
";
cin>>FilePath;
m_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);//Ifthefunctionsucceeds,thereturnvalueisahandle
//totheeventobject.
//创立一个空事件对象,返回一个句柄
CreateThread(NULL,0,ARecv,NULL,0,NULL);//TheCreateThreadfunctioncreatesathreadtoexecute
//withinthevirtualaddressspaceofthecallingprocess.
//执行ARecv函数
if(!
ASendto())
{
cout<<"filesendfailed"<getchar();
}
getchar();
getchar();
return0;
}
接收端程序:
//receiver.cpp:
定义控制台应用程序的入口点。
#include"stdafx.h"
#include
#include
#include"../header/ARQ.h"
#include"../header/Exception.h"
//效劳器端口
#defineSERVER_PORT2280
#pragmament(lib,"ws2_32.lib")//设置link时的lib库
usingnamespacestd;
SOCKETPrimaryUDP;
charFileSavePath[MAX_PATH];
voidInitWinSock()
{
WSADATAwsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!
=0)
{
throwException("Windowssockets2.2startupunsuccessful");
}
else{
printf("Using%s(Status:
%s)\n",
wsaData.szDescription,wsaData.szSystemStatus);
printf("withAPIversions%d.%dto%d.%d\n\n",
LOBYTE(wsaData.wVersion),HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion),HIBYTE(wsaData.wHighVersion));
}
}
voidmksock(inttype)
{
PrimaryUDP=socket(AF_INET,type,0);
if(PrimaryUDP<0)
{
throwException("createsocketerror");
}
}
voidBindSock()
{
sockaddr_insin;
sin.sin_addr.S_un.S_addr=INADDR_ANY;
sin.sin_family=AF_INET;
sin.sin_port=htons(SERVER_PORT);
if(bind(PrimaryUDP,(structsockaddr*)&sin,sizeof(sin))<0)
throwException("binderror");
}
DWORDWINAPIARecv(LPVOIDlpParam)
{
FILE*file=NULL;
sockaddr_inremote;
intsinlen=sizeof(remote);
BSCbuffer,bsc;
bsc.header=STX;bsc.tail=ETX;
memset(bsc.data,0,MAXBSCLENGTH);
intiread=0;
unsignedlongdwReceive