当前位置:文档之家› 滑动窗口协议仿真

滑动窗口协议仿真

滁州学院课程设计报告课程名称:计算机网络设计题目:滑动窗口协议仿真系别:计算机与信息工程学院专业:计算机科学与技术组别:第五组起止日期: 2011年11月24日~2011年12月7日指导教师:赵国柱计算机与信息工程学院二○一一年制课程设计任务书一. 引言二. 基本原理2.1 窗口机制2.2 1bit滑动窗口协议2.3 后退N协议2.4 选择重传协议2.5 流量控制三. 需求分析3.1 课程设计题目3.2 开发环境3.3 运行环境3.4 课程设计任务及要求3.5 界面要求3.6 网络接口要求四. 详细设计4.1 结构体的定义4.2 发送方的主要函数4.3 接受方的主要函数五.源代码5.1 发送方的主要代码5.2 接收方的主要代码六. 调试与操作说明致谢[参考文献]课程设计的主要内容1.引言早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。

由于大家不知道网络拥塞状况,一起发送数据,导致中间结点阻塞掉包,谁也发不了数据。

在数据传输过程中,我们总是希望数据传输的更快一些,但如果发送方把数据发送的过快,接收方就可能来不及接收,这就造成数据的丢失。

因此就有了滑动窗口机制来解决这些问题。

早期我们使用的是1bit滑动窗口协议,一次只发送一个帧,等收到ack确认才发下一个帧,这样对信道的利用率太低了。

因此提出了一种采用累积确认的连续ARQ协议,接收方不必对收到的帧逐个发送ack确认,而是收到几个帧后,对按序到达的最后一个帧发送ack确认。

同1bit滑动窗口协议相比,大大减少了ack数量,并消除了延迟ack对传输效率的影响。

2.基本原理2.1 窗口机制滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。

发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。

不同的滑动窗口协议窗口大小一般不同。

发送方窗口内的序号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。

接受方为其窗口内的每一个序号保留了一个缓冲区。

与每个缓冲区相关联的还有一位,用来指明该缓冲区是满的还是空的。

2.2 1bit滑动窗口协议当发送窗口和接收窗口的大小固定为1时,滑动窗口协议退化为停等协议(stop-and-wait)。

该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才能继续发送下一帧。

由于接收方需要判断接收到的帧是新发的帧还是重新发送的帧,因此发送方要为每一个帧加一个序号。

由于停等协议规定只有一帧完全发送成功后才能发送新的帧,因而只用一比特来编号就够了。

其发送方和接收方运行的流程图如图所示。

2.3 后退N协议由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。

后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧,也可以继续发送。

且发送方在每发送完一个数据帧时都要设置超时定时器。

只要在所设置的超时时间内仍收到确认帧,就要重发相应的数据帧。

如:当发送方发送了N个帧后,若发现该N帧的前一个帧在计时器超时后仍未返回其确认信息,则该帧被判为出错或丢失,此时发送方就不得不重新发送出错帧及其后的N帧。

从这里不难看出,后退n协议一方面因连续发送数据帧而提高了效率,但另一方面,在重传时又必须把原来已正确传送过的数据帧进行重传(仅因这些数据帧之前有一个数据帧出了错),这种做法又使传送效率降低。

由此可见,若传输信道的传输质量很差因而误码率较大时,连续测协议不一定优于停止等待协议。

此协议中的发送窗口的大小为k,接收窗口仍是1。

2.4 选择重传协议在后退n协议中,接收方若发现错误帧就不再接收后续的帧,即使是正确到达的帧,这显然是一种浪费。

另一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。

一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。

这种方法称为选择重发(SELECTICE REPEAT),其工作过程如图所示。

显然,选择重发减少了浪费,但要求接收方有足够大的缓冲区空间。

2.5 流量控制TCP的特点之一是提供体积可变的滑动窗口机制,支持端到端的流量控制。

TCP的窗口以字节为单位进行调整,以适应接收方的处理能力。

处理过程如下:(1)TCP连接阶段,双方协商窗口尺寸,同时接收方预留数据缓存区;(2)发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认;(3)发送方根据确认信息,改变窗口的尺寸,增加或者减少发送未得到确认的字节流中的字节数。

调整过程包括:如果出现发送拥塞,发送窗口缩小为原来的一半,同时将超时重传的时间间隔扩大一倍。

(4)滑动窗口机制为端到端设备间的数据传输提供了可靠的流量控制机制。

然而,它只能在源端设备和目的端设备起作用,当网络中间设备(例如路由器等)发生拥塞时,滑动窗口机制将不起作用。

3.需求分析3.1 课程设计题目:滑动窗口协议仿真3.2 开发环境:Visual C++ 6.03.3 运行环境:Windows操作系统3.4 课程设计任务及要求:(1)程序按照滑动窗口协议实现端对端的数据传送。

包括协议的各种策略,如包丢失、停等应答、超时等都应有所仿真实现。

(2)显示数据传送过程中的各项具体数据。

双方帧的个数变化,帧序号,发送和接受速度,暂停或重传提示等。

3.5 界面要求:此次课程设计要求的所有功能应可视,我们组主要是用VC++编写的,运行在DOS环境下,观察发送方(sender)发送数据包到接收方(receive)时。

3.6 网络接口要求:两台机器或是一台机器中两个独立的线程模拟发送方与接受方,接收数据的端口初始应为监听状态。

发送方向接受方发起连接,成功后开始发送数据。

4.概要设计4.1 结构体定义如下:typedef enum {data = 1,ack,nak,tout} frame_kind; //帧类型typedef struct frame_head{frame_kind kind; //帧类型unsigned int seq; //序列号unsigned int ack; //确认号unsigned char data[MAX_LENGTH]; //数据}Head;typedef struct frame{frame_head head; //帧头unsigned int size; //数据的大小} Frame;typedef struct framenode //队列节点类型{frame head_data;struct framenode *next;} Framenode;typedef struct{Framenode *front; //队头指针Framenode *rear; //队尾指针} LinkQueue;4.2 发送方的主要函数实现:函数名:void InitLine(LinkQueue *q);功能:初始化队列。

函数名:void GetFrameFromHost(LinkQueue *q);功能:从主机取数据帧,由于实验需要,假设主机有足够多的数据帧要发送。

void DeLine(LinkQueue *q);功能:数据帧发送完毕(收到确认帧)后,删除发送的数据帧(队头)。

函数名:int QueueEmpty(LinkQueue *q);功能:判断队列是否为空。

函数名:frame QueueFront(LinkQueue *q);功能:取队头,首帧是准备好待发送的帧。

函数名:int QueueLen(LinkQueue *q);功能:计算队列长度。

函数名:DWORD WINAPI ReceiveFun(LPVOID pArg);功能:发送线程调用的函数,pArg参数存接收帧指针。

函数名:void main();功能:发送方主函数,首先和接收方(本机"127.0.0.1")建立socket连接并初始化发送队列。

然后重复下面的步骤:(1)从主机取数据帧;(2)发送数据帧,含超时重发(接收方未收到或未收到接收方ack)和错误重发(收到接收方nak);(3)设置超时计时器,这里是5秒;(4)等待确认,调用CreateThread()函数创建一个线程,超时则调用TerminateThread()函数结束线程并再次发送数据帧。

收到数据帧则做后续处理;(5)收到否认帧nak则再次发送数据帧,收到确认帧ack则发送下一个数据帧;(6)如果发送的测试时间达到20秒,则提示是否继续测试,按‘q’或‘Q’退出测试。

4.3接收方的主要函数实现:函数名:void InitLine(LinkQueue *q);功能:初始化队列。

函数名:void GetFrameFromHost(LinkQueue *q);功能:准备好接收帧的缓冲池,首帧是待接收的帧,尾帧是已经接收的待提交主机的帧。

由于实验需要,假设数据帧送往主机是足够快的。

int DeLine(LinkQueue *q, frame *pf, unsigned int curw)功能:将帧数据保存供提交主机,curw是打开的待接收数据的窗口。

函数名:int QueueEmpty(LinkQueue *q);功能:判断队列是否为空。

函数名:int QueueLen(LinkQueue *q);功能:计算队列长度。

函数名:void main();功能:接收方主函数,首先和发送方建立socket连接并初始化初始化接收窗口。

然后重复下面的步骤:(1)等待,接收数据帧;(2)校验数据帧,假定产生随机结果,20%的概率校验错误或发送方发送数据帧超时;(3)校验错误时,丢弃数据帧,并发送否认帧nak;(4)如果出现接收超时(假定未收到发送方发送的数据帧),则不给发送发任何回应;(5)如果校验正确,首先判断是否是上一帧的重发。

是上一帧的重发,则丢弃数据帧,并发送确认帧ack;是新的数据帧,则保存数据帧到当前接收窗口,并发送确认帧ack。

(6)送数据帧至主机。

5.源代码5.1 发送方的主要代码:void InitLine(LinkQueue *q){q->front = q->rear = NULL;}int QueueEmpty(LinkQueue *q){return q->front == NULL && q->rear == NULL; }frame QueueFront(LinkQueue *q){if (QueueEmpty(q)){printf("队列为空!\n");Sleep(SLEEPMS);exit(0);}return q->front->head_data;}int QueueLen(LinkQueue *q){if (QueueEmpty(q)){return 0;}int num = 0;Framenode *p = q->front;while(p != NULL){num++;p = p->next;}return num;}void GetFrameFromHost(LinkQueue *q){if(QueueLen(q) >= MAXPOOL){printf("data %d 已准备好\n", q->front->head_data.head.seq);return;}Framenode *p=(Framenode *)malloc(sizeof(Framenode));memset(p->head_data.head.data, 0, MAX_LENGTH);srand((unsigned)time(NULL));p->head_data.size = rand() % MAX_LENGTH; // 帧大小随机生成memset(p->head_data.head.data, '1', p->head_data.size);p->head_data.head.ack = -1;p->head_data.head.kind = data;p->head_data.head.seq = 0;p->next =NULL;if(QueueEmpty(q))q->front = q->rear=p; // 首帧是待发送的帧else{p->head_data.head.seq = (q->rear->head_data.head.seq + 1)%MAXPOOL;q->rear->next =p;q->rear =p;}printf("从主机得到:data %d,放入缓存\n", p->head_data.head.seq);GetFrameFromHost(q); // 由于实验需要,假设主机有足够多的数据帧要发送}void DeLine(LinkQueue *q){Framenode *p = NULL;if(QueueEmpty(q)){printf("队列为空!\n");}else{p = q->front;q->front = p->next;if (q->rear == p) q->rear = NULL;printf("发送data %d, %d 成功!从缓存中删除\n", p->head_data.head.seq, p->head_data.size);free(p);p = NULL;}}void main(){printf("建立连接... \n");Begin:WORD wVersionRequested;WSADATA wsaData; //初始化socket库wVersionRequested=MAKEWORD(1,1); //两个byte型合并成一个WORD型int err=WSAStartup(wVersionRequested,&wsaData);if(err!=0){Sleep(SLEEPMS);return;}if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ){WSACleanup(); //中止Windows Sockets服务WSAStartup()成对使用Sleep(SLEEPMS);return;}socketClient = socket(AF_INET,SOCK_STREAM,0);//监听的套接字SOCKADDR_IN clientadd;clientadd.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");clientadd.sin_family = AF_INET;clientadd.sin_port = htons(7001);//设置连接端的IP、端口if(SOCKET_ERROR==connect(socketClient,(SOCKADDR*)&clientadd,sizeof(SOCKADDR)) ) //连接{WSACleanup();Sleep(SLEEPMS);goto Begin;}char getData[RECEIVE_MAX_LENGTH];memset(getData, 0, RECEIVE_MAX_LENGTH); //清零if(recv(socketClient,getData,RECEIVE_MAX_LENGTH,0) == SOCKET_ERROR) //接受{printf("接受连接提示信息出错!\n");}else{printf("%s\n",getData);}char sendData[SEND_MAX_LENGTH];memset(sendData, 0, SEND_MAX_LENGTH);strcpy(sendData, "你好接收方,我是发送方!");if( SOCKET_ERROR == send(socketClient,sendData,strlen(sendData)+1,0) ) //发送{printf("发送连接提示信息出错!\n");WSACleanup();closesocket(socketClient);Sleep(SLEEPMS);return;}printf("按任意键继续!\n");while (!kbhit()) {}; //等待开始Sleep(SLEEPMS);printf("1bit滑动窗口协议:发送方,发送窗口=1\n");LinkQueue QueueQ;InitLine(&QueueQ);frame packetsend; //dataframe packetreceive; // ack,nakunsigned long tick = GetTickCount();int ret = 0;HANDLE hThread;while(1){GetFrameFromHost(&QueueQ); //从主机取数据帧memset(&packetsend, 0, sizeof(packetsend));Sleep(SLEEPMS);printf("\n");packetsend = QueueFront(&QueueQ); //取数据帧ret = send(socketClient, (char *)&packetsend, sizeof(packetsend), 0);//发送data if(ret == SOCKET_ERROR){printf("发送数据出错!\n");continue;}printf("发送数据帧:data %d, %d\n", packetsend.head.seq, packetsend.size);const unsigned long timeOut = 5 * 1000; //设置超时计时器5秒超时memset(&packetreceive, 0, sizeof(packetreceive));Sleep(SLEEPMS);printf("\n");InitializeCriticalSection(&gCS); // 初始化临界区hThread=CreateThread(NULL, 0, ReceiveFun, (LPVOID)&packetreceive, 0, NULL);int r = WaitForMultipleObjects(1, &hThread, TRUE, timeOut);DeleteCriticalSection(&gCS); //与InitializeCriticalSection(&gCS);成对使用if(ret == SOCKET_ERROR || ret == SOCKET_DISCONN){printf("接受出错!Press any key to continue\n");while (!kbhit()) {};continue;}if(r == WSA_W AIT_TIMEOUT) //判断超时{TerminateThread(hThread, 0); //终止线程printf("超时重传:data %d, %d\n", packetsend.head.seq,packetsend.size);}else if(packetsend.head.seq == packetreceive.head.ack){srand((unsigned)time(NULL));switch(rand() % 5) //假定产生随机结果,20%的概率超时{case 0:printf("接收方发送回复超时(ack丢失模拟):%d\n", packetsend.head.seq);printf("超时重传:data %d, %d\n", packetsend.head.seq,packetsend.size);break;default:if(packetreceive.head.kind == ack){printf("接受ack帧:ack %d\n", packetreceive.head.ack);DeLine(&QueueQ);}else if(packetreceive.head.kind == nak){printf("接受nak帧:nak %d\n", packetsend.head.seq);}break;}}else printf("帧序号出错:%d\n", packetreceive.head.ack);if(GetTickCount() - tick > 20 * TIMEOUT) //设置时间20秒{printf("持续时间20s. 按q退出,其他键继续\n");int kbc = getch();if(kbc == 'q' || kbc == 'Q')break;}}printf("按任意键退出!\n");while (!kbhit()) {};Sleep(SLEEPMS);printf("谢谢使用!\n");WSACleanup();closesocket(socketClient);Sleep(SLEEPMS);}DWORD WINAPI ReceiveFun(LPVOID pArg)EnterCriticalSection(&gCS);//进入critical sectionframe *packetreceive = (frame *)pArg;ret = recv(socketClient, (char *)packetreceive, sizeof(*packetreceive), 0);LeaveCriticalSection(&gCS); //线程用毕,离开critical sectionreturn ret;}5.2 接收方的主要代码:void InitLine(LinkQueue *q){q->front = q->rear = NULL;}int QueueEmpty(LinkQueue *q){return q->front == NULL && q->rear == NULL;}frame QueueFront(LinkQueue *q){if (QueueEmpty(q)){printf("队列为空!\n");Sleep(SLEEPMS);exit(0);}return q->front->head_data;}int QueueLen(LinkQueue *q)if (QueueEmpty(q)){return 0;}int num = 0;Framenode *p = q->front;while(p != NULL){num++;p = p->next;}return num;}int GetFrameFromHost(LinkQueue *q){if(QueueLen(q) >= MAXPOOL){printf("准备接受:data %d \n", q->front->head_data.head.seq);return q->front->head_data.head.seq;}Framenode *p=(Framenode *)malloc(sizeof(Framenode));memset(p->head_data.head.data, 0, MAX_LENGTH);p->head_data.head.ack = -1;p->head_data.head.kind = ack;p->head_data.head.seq = 0;p->next =NULL;if(QueueEmpty(q))q->front = q->rear=p;else{p->head_data.head.seq = (q->rear->head_data.head.seq + 1)%MAXPOOL;q->rear->next =p;q->rear = p;}return GetFrameFromHost(q);}int DeLine(LinkQueue *q, frame *pf, unsigned int curw) //假设数据帧送往主机是足够快的{Framenode *p = NULL;if(curw == q->front->head_data.head.seq)p = q->front;elsep = q->rear;if(p->head_data.head.ack != -1) //假定数据已经提交主机{printf("向主机交付data %d, %d 成功!\n", p->head_data.head.ack, p->head_data.size);}memset(p->head_data.head.data, 0, MAX_LENGTH);memcpy(p->head_data.head.data, pf->head.data, pf->size);p->head_data.size = pf->size;p->head_data.head.ack = pf->head.seq; //保存发送帧序号return p->head_data.head.seq;}frame QueueAnswer(LinkQueue *q, unsigned int curw){if(curw == q->front->head_data.head.seq){return q->front->head_data;}else{return q->rear->head_data;}}void main(){Begin:WORD wVersionRequested;WSADATA wsaData; //初始化socket库wVersionRequested = MAKEWORD( 1, 1 ); //两个byte型合并成一个WORD型int err = WSAStartup(wVersionRequested, &wsaData );//使用sockets之前要调用一次if ( err != 0 ){Sleep(SLEEPMS);return;}if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ){WSACleanup();//中止Windows Sockets服务WSAStartup()成对使用Sleep(SLEEPMS);return;}SOCKET socksrv = socket(AF_INET,SOCK_STREAM,0);//监听的套接字SOCKADDR_IN socketadd;socketadd.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //监听连接socketadd.sin_family = AF_INET;socketadd.sin_port = htons(7001); //设置端口if( SOCKET_ERROR == bind(socksrv,(SOCKADDR*)&socketadd,sizeof(SOCKADDR)) ) {printf("绑定出错!\n");WSACleanup();Sleep(SLEEPMS);return;}if( SOCKET_ERROR == listen(socksrv,5) ){printf("监听出错!");WSACleanup();Sleep(SLEEPMS);return;}SOCKADDR_IN sockclient;int len = sizeof(SOCKADDR);SOCKET sockconn = accept(socksrv,(SOCKADDR*)&sockclient,&len);//建立连接的套节字if(INV ALID_SOCKET == sockconn ){printf("建立连接出错!\n");return;}char sendData[SEND_MAX_LENGTH];memset(sendData, 0, SEND_MAX_LENGTH);sprintf(sendData,"%s","你好发送方,我是接受方!");if( SOCKET_ERROR == send(sockconn,sendData,strlen(sendData)+1,0) ) {printf("发送连接提示信息出错!\n");WSACleanup( );closesocket(sockconn);Sleep(SLEEPMS);return;}char getData[RECEIVE_MAX_LENGTH];memset(getData, 0, RECEIVE_MAX_LENGTH);recv(sockconn,getData,RECEIVE_MAX_LENGTH,0);printf("%s\n",getData);printf("1bit滑动窗口协议:接收方,接收窗口=1\n");LinkQueue QueueQ;InitLine(&QueueQ);frame packetreceive; //dataframe packetsend; // ack,nakint curw = GetFrameFromHost(&QueueQ);//初始化接收窗口int ret = 0;while(1){memset(&packetreceive, 0, sizeof(packetreceive));Sleep(SLEEPMS);printf("\n");ret = recv(sockconn,(char *)&packetreceive, sizeof(packetreceive), 0); if(ret == SOCKET_ERROR || ret == SOCKET_DISCONN){if(ret == SOCKET_ERROR){printf("连接出错!自动连接!\n");continue;}else{printf("连接已断开,按q退出,其他键等待新的连接\n");int kbc = getch();if(kbc == 'q' || kbc == 'Q')break;else{WSACleanup();closesocket(sockconn);Sleep(SLEEPMS);goto Begin;}}}srand((unsigned)time(NULL));switch(rand() % 5) //假定产生随机结果,20%的概率校验错误或接收发送方超时{case 0:printf("接受数据帧:data %d, %d,校验错误,丢弃(数据帧出错模拟)\n", packetreceive.head.seq, packetreceive.size);memset(&packetsend, 0, sizeof(packetsend));memcpy(&packetsend, &packetreceive, sizeof(packetreceive));packetsend.head.ack = packetreceive.head.seq;packetsend.head.seq = curw;packetsend.head.kind = nak;printf("发送否认帧:nak %d\n", packetreceive.head.seq);break;case 1:packetsend.head.kind = tout;printf("发送方发送数据超时(数据帧丢失模拟):%d\n", packetreceive.head.seq);break;default:printf("接受数据帧:data %d, %d,校验正确\n", packetreceive.head.seq, packetreceive.size);if(packetreceive.head.seq == (QueueAnswer(&QueueQ, curw)).head.ack){printf("上一帧的重发,丢弃,直接发送确认帧:ack %d\n", packetreceive.head.seq);}else{printf("新的数据帧:data %d, %d,放入缓存\n", packetreceive.head.seq, packetreceive.size);curw = DeLine(&QueueQ, &packetreceive, curw); //将新帧保存待送往主机memset(&packetsend, 0, sizeof(packetsend));packetsend = QueueAnswer(&QueueQ, curw); //待发送的确认帧printf("发送确认帧:ack %d\n", packetreceive.head.seq);}packetsend.head.kind = ack;break;}if(packetsend.head.kind == tout) continue; //发送方使用多线程判断超时ret = send(sockconn, (char *)&packetsend, sizeof(packetsend), 0);if(ret == SOCKET_ERROR){printf("发送ack或nak出错!自动连接!\n");continue;}}6.调试与操作说明用户打开接收方程序时会自动等待发送方,打开发送方程序时会自动尝试连接接收方程序。

相关主题