资源描述
滁州学院
课程设计汇报
课程名称: 计算机网络
设计题目: 滑动窗口协议仿真
系 别: 计算机与信息工程学院
专 业: 计算机科学与技术
组 别: 第五组
起止日期: 11月24日~12月7日
指导老师: 赵国柱
计算机与信息工程学院二○一一年制
课程设计题目
滑动窗口协议仿真
组长
赵育坤
学号
班级
计专1班
系别
计算机与信息工程学院
专业
计算机科学与技术
组员
闫婷、 张侠、 余静、 于东锋、 张飞、 赵育坤
指导老师
赵国柱
课程设计目
掌握滑动窗口协议基础原理, 并能够用所学计算机高级语言进行编程模拟
课程设计所需环境
开发环境: VC++ 运行环境:Windows 操作系统
课程设计任务要求
1.程序根据滑动窗口协议实现端对端数据传送。包含协议多种策略, 如包丢失、 停等应答、 超时等都应有所仿真实现
2.显示数据传送过程中各项具体数据。双方帧个数改变, 帧序号, 发送和接收速度, 暂停或重传提醒等
课程设计工作进度计划
序号
起止日期
工 作 内 容
分工情况
1
11月24号~11月27号
了解工作要求, 明确分工内容, 网上查阅相关资料
全部组员共同参与
2
11月28号~11月30号
sender队列模块编写
由闫婷完成
3
12月1号~12月4号
sender主函数编写
由赵育坤、 张飞完成
4
11月28号~11月30号
receiver队列模块编写
由张侠完成
5
12月1号~12月4号
receiver主函数编写
由余静、 于东锋完成
6
12月5号~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对传输效率影响。不过, 这会产生一个新问题, 假如发送方发送了5个帧, 而中间第3个帧丢失了。这时接收方只能对前2个帧发出确定。发送方无法知道后面三个帧下落, 只好把后面3个帧再重传一次, 这就是回退N协议。为了处理这个问题, 又提出了选择重传协议。当接收方发觉某帧犯错后, 继续接收后面送来正确帧, 只是不交付它们, 存放在自己缓冲区中, 而且要求发送方重传犯错那一帧。一旦收到重传来帧后, 就能够将存于缓冲区中其它帧一并按正确次序递交给主机。
2.基础原理
2.1 窗口机制
滑动窗口协议基础原理就是在任意时刻, 发送方都维持了一个连续许可发送帧序号, 称为发送窗口; 同时, 接收方也维持了一个连续许可接收帧序号, 称为接收窗口。发送窗口和接收窗口序号上下界不一定要一样, 甚至大小也能够不一样。不一样滑动窗口协议窗口大小通常不一样。发送方窗口内序号代表了那些已经被发送, 不过还没有被确定帧, 或者是那些能够被发送帧。接收方为其窗口内每一个序号保留了一个缓冲区。与每个缓冲区相关联还有一位, 用来指明该缓冲区是满还是空。
若从滑动窗口见解来统一看待1比特滑动窗口、 后退n及选择重传三种协议, 它们差异仅在于各自窗口尺寸大小不一样而已。1比特滑动窗口协议: 发送窗口=1, 接收窗口=1; 后退N协议: 发送窗口>1, 接收窗口=1; 选择重传协议: 发送窗口>1,接收窗口>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.0
3.3 运行环境: Windows 操作系统
3.4 课程设计任务及要求:
(1)程序根据滑动窗口协议实现端对端数据传送。包含协议多种策略, 如包丢失、 停等应答、 超时等都应有所仿真实现。
(2)显示数据传送过程中各项具体数据。双方帧个数改变, 帧序号, 发送和接收速度, 暂停或重传提醒等。
3.5 界面要求:
此次课程设计要求全部功效应可视, 我们组关键是用VC++编写, 运行在DOS环境下, 观察发送方(sender)发送数据包到接收方(receive)时。界面应显示出双方帧个数改变, 帧序号, 发送和接收速度, 暂停或重传提醒等, 界面中必需动态显示数据帧发送和接收情况, 包含在对应窗口具体显示对应ACK和其她收发数据帧后发出消息, 以表明模拟协议正确运作过程。在多种情况下, 接收方和发送方窗口应实时显示帧发送和接收情况, 包含序号, 时间戳, 内容等。以及窗口填充和清空情况。
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; //data
frame packetreceive; // ack,nak
unsigned 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_WAIT_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 section
frame *packetreceive = (frame *)pArg;
ret = recv(socketClient, (char *)packetreceive, sizeof(*packetreceive), 0);
LeaveCriticalSection(&gCS); //线程用毕, 离开critical section
return 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;
else
p = 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(INVALID_SOCKET == sockconn )
{
printf("建立连接犯错! \n");
WSACleanup( );
Sleep(SLE
展开阅读全文