计算机网络之GBN协议

来源:互联网 发布:杭州认知网络 编辑:程序博客网 时间:2024/05/23 09:30

客户端程序:

//#include "stdafx.h" //创建 VS 项目包含的预编译头文件#include <stdlib.h>#include <time.h>#include <WinSock2.h>#include <fstream>#include<iostream>#include<stdio.h>#include <io.h> #pragma comment(lib,"ws2_32.lib")#define SERVER_PORT  12340  //端口号#define SERVER_IP  "0.0.0.0" //IP 地址const int BUFFER_LENGTH = 1026; //缓冲区大小, (以太网中 UDP 的数据帧中包长度应小于 1480 字节)const int SEND_WIND_SIZE = 10;//发送窗口大小为 10,GBN 中应满足 W + 1 <=N(W 为发送窗口大小,N 为序列号个数)//本例取序列号 0...19 共 20 个//如果将窗口大小设为 1,则为停-等协议const int SEQ_SIZE = 20; //序列号的个数,从 0~19 共计 20 个//由于发送数据第一个字节如果值为 0, 则数据会发送失败//因此接收端序列号为 1~20,与发送端一一对应BOOL ack[SEQ_SIZE];//收到 ack 情况,对应 0~19 的 ackint curSeq;//当前数据包的 seqint curAck;//当前等待确认的 ackint totalSeq;//收到的包的总数int totalPacket;//需要发送的包总数int size;int flag = 0; //标识是否收到最后一个ack//************************************// Method: getCurTime// FullName: getCurTime// Access: public// Returns: void// Qualifier: 获取当前系统时间,结果存入 ptime 中// Parameter: char * ptime//************************************void getCurTime(char *ptime) {char buffer[128];memset(buffer, 0, sizeof(buffer));time_t c_time;struct tm *p;p = new tm;time(&c_time);localtime_s(p, &c_time);sprintf_s(buffer, "%d/%d/%d %d:%d:%d",p->tm_year + 1900,p->tm_mon,p->tm_mday,p->tm_hour,p->tm_min,p->tm_sec);strcpy_s(ptime, sizeof(buffer), buffer);}//************************************// Method: seqIsAvailable// FullName: seqIsAvailable// Access: public// Returns: bool// Qualifier: 当前序列号 curSeq 是否可用//************************************bool seqIsAvailable() {int step;step = curSeq - curAck;step = step >= 0 ? step : step + SEQ_SIZE;//序列号是否在当前发送窗口之内if (step >= SEND_WIND_SIZE) {return false;}if (ack[curSeq]) {return true;}return false;}//************************************// Method: timeoutHandler// FullName: timeoutHandler// Access: public// Returns: void// Qualifier: 超时重传处理函数,滑动窗口内的数据帧都要重传//************************************void timeoutHandler() {printf("Timer out error.\n");int index;for (int i = 0; i< SEND_WIND_SIZE; ++i) {index = (i + curAck) % SEQ_SIZE;ack[index] = TRUE;}totalSeq -= SEND_WIND_SIZE;curSeq = curAck;}//************************************// Method: ackHandler// FullName: ackHandler// Access: public// Returns: void// Qualifier: 收到 ack,累积确认,取数据帧的第一个字节//由于发送数据时,第一个字节(序列号)为 0(ASCII)时发送失败,因此加一了,此处需要减一还原// Parameter: char c//************************************void ackHandler(char c) {unsigned char index = (unsigned char)c - 1; //序列号减一printf("Recv a ack of %d\n", index);if (index >= (size / 1024))flag = 1;if (curAck <= index) {for (int i = curAck; i <= index; ++i) {ack[i] = TRUE;}curAck = (index + 1) % SEQ_SIZE;}else {//ack 超过了最大值,回到了 curAck 的左边for (int i = curAck; i< SEQ_SIZE; ++i) {ack[i] = TRUE;}for (int i = 0; i <= index; ++i) {ack[i] = TRUE;}curAck = index + 1;}}//主函数int main(int argc, char* argv[]){//加载套接字库(必须)WORD wVersionRequested;WSADATA wsaData;//套接字加载时错误提示int err;//版本 2.2wVersionRequested = MAKEWORD(2, 2);//加载 dll 文件 Scoket 库err = WSAStartup(wVersionRequested, &wsaData);if (err != 0) {//找不到 winsock.dllprintf("WSAStartup failed with error: %d\n", err);return -1;}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("Could not find a usable version of Winsock.dll\n");WSACleanup();}else {printf("The Winsock 2.2 dll was found okay\n");}SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//设置套接字为非阻塞模式int iMode = 1; //1:非阻塞,0:阻塞//ioctlsocket()是控制套接口的模式。//int ioctlsocket( int s, long cmd, u_long * argp);//s:一个标识套接口的描述字。//cmd:对套接口s的操作命令。//argp:指向cmd命令所带参数的指针。ioctlsocket(sockServer, FIONBIO, (u_long FAR*) &iMode);//非阻塞设置SOCKADDR_IN addrServer; //服务器地址//addrServer.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//两者均可addrServer.sin_family = AF_INET;addrServer.sin_port = htons(SERVER_PORT);err = bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));if (err) {err = GetLastError();printf("Could  not  bind  the  port  %d  for  socket.Error  code is %d\n",SERVER_PORT,err);WSACleanup();return -1;}SOCKADDR_IN addrClient; //客户端地址int length = sizeof(SOCKADDR);char buffer[BUFFER_LENGTH]; //数据发送接收缓冲区ZeroMemory(buffer, sizeof(buffer));//将测试数据读入内存std::ifstream icin;icin.open("../test.txt");FILE *file;//获取文件大小fopen_s(&file,"../test.txt", "rb");size = _filelength(_fileno(file));printf("文件大小 :%d \n",size);fclose(file);//char data[1024 * 113];char *data = new char[size];ZeroMemory(data, sizeof(data));icin.read(data, size); //读取整个的文件内容icin.close();//发送包数目  totalPacket = sizeof(data) / 1024;int recvSize;for (int i = 0; i < SEQ_SIZE; ++i) {ack[i] = TRUE;}while (true) {//非阻塞接收,若没有收到数据,返回值为-1recvSize =recvfrom(sockServer, buffer, BUFFER_LENGTH, 0, ((SOCKADDR*)&addrClient), &length);if (recvSize < 0) {Sleep(200);continue;}printf("recv from client: %s\n", buffer);if (strcmp(buffer, "-time") == 0) { //请求服务器时间getCurTime(buffer);}else if (strcmp(buffer, "-quit") == 0) { //退出strcpy_s(buffer, strlen("Good bye!") + 1, "Good bye!");}else if (strcmp(buffer, "-testgbn") == 0) { //gbn数据发送//进入 gbn 测试阶段//首先 server(server 处于 0 状态)向 client 发送 205 状态码(server 进入 1 状态)//server 等待 client 回复 200 状态码, 如果收到 (server 进入 2 状态),则开始传输文件,否则延时等待直至超时\//在文件传输阶段,server 发送窗口大小设为ZeroMemory(buffer, sizeof(buffer));int recvSize;int waitCount = 0;printf("Begain to test GBN protocol,please don't abort the process\n");//加入了一个握手阶段//首先服务器向客户端发送一个 205 大小的状态码(我自己定义的)表示服务器准备好了,可以发送数据//客户端收到 205 之后回复一个 200 大小的状态码,表示客户端准备好了,可以接收数据了//服务器收到 200 状态码之后,就开始使用 GBN 发送数据了printf("Shake hands stage\n");int stage = 0;bool runFlag = true;while (runFlag) {switch (stage) {case 0://发送 205 阶段buffer[0] = 205;sendto(sockServer, buffer, strlen(buffer) + 1, 0,(SOCKADDR*)&addrClient, sizeof(SOCKADDR));Sleep(100);stage = 1; //发送205,准备接收200break;case 1://等待接收 200 阶段,没有收到则计数器+1,超时则放弃此次“连接”,等待从第一步开始recvSize =recvfrom(sockServer, buffer, BUFFER_LENGTH, 0, ((SOCKADDR*)&addrClient), &length);if (recvSize < 0) {++waitCount; //计时if (waitCount > 20) {  //超时runFlag = false;printf("Timeout error\n");break;}Sleep(500);continue;}else {if ((unsigned char)buffer[0] == 200) {printf("Begin a file transfer\n");printf("File size is %dB, each packet is 1024B and packet total num is %d\n",sizeof(data),totalPacket);curSeq = 0;curAck = 0;totalSeq = 0;waitCount = 0;stage = 2; //开始之后,进入数据传输阶段,先修改stage}}break;case 2://数据传输阶段if (seqIsAvailable()) {  //当前序列号可用//发送给客户端的序列号从 1 开始buffer[0] = curSeq + 1;ack[curSeq] = FALSE;//数据发送的过程中应该判断是否传输完成//为简化过程此处并未实现memcpy(&buffer[1], data + 1024 * totalSeq, 1024);printf("send a packet with a seq of %d\n", curSeq + 1); //客户端是 1-20,此处加一是为了与客户端统一sendto(sockServer, buffer, BUFFER_LENGTH, 0,(SOCKADDR*)&addrClient, sizeof(SOCKADDR));++curSeq;curSeq %= SEQ_SIZE;++totalSeq;Sleep(500);}//等待 Ack,若没有收到,则返回值为-1,计数器+1recvSize = recvfrom(sockServer, buffer, BUFFER_LENGTH, 0, ((SOCKADDR*)&addrClient), &length);if (recvSize < 0) {waitCount++;//20 次等待 ack 则超时重传if (waitCount > 20){timeoutHandler();//超时重传waitCount = 0;}}else {//收到 ackackHandler(buffer[0]);waitCount = 0;}Sleep(500);break;}if (flag == 1){//结束int stage = 0;strcpy_s(buffer, strlen("Good bye!") + 1, "Good bye!");printf("%s \n", buffer);runFlag = false;break;}}if (flag ==1){   //传输结束break;//printf("dsadasd\n");}}sendto(sockServer, buffer, strlen(buffer) + 1, 0, (SOCKADDR*)&addrClient,sizeof(SOCKADDR));Sleep(500);}//关闭套接字,卸载库closesocket(sockServer);WSACleanup();system("pause");return 0;}


服务器端程序:

// GBN_client.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <stdlib.h>#include<stdio.h>#include <WinSock2.h>#include <time.h>#include <WS2tcpip.h>#pragma comment(lib,"ws2_32.lib")#define SERVER_PORT  12340 //接收数据的端口号#define SERVER_IP  "127.0.0.1" // 服务器的 IP 地址const int BUFFER_LENGTH = 1026;const int SEQ_SIZE = 20;//接收端序列号个数,为 1~20/****************************************************************//* -time 从服务器端获取当前时间-quit 退出客户端-testgbn [X] 测试 GBN 协议实现可靠数据传输[X] [0,1] 模拟数据包丢失的概率[Y] [0,1] 模拟 ACK 丢失的概率*//****************************************************************/void printTips() {printf("*****************************************\n");printf("| -time to get current time |\n");printf("| -quit to exit client |\n");printf("| -testgbn [X] [Y] to test the gbn |\n");printf("*****************************************\n");}//************************************// Method: lossInLossRatio// FullName: lossInLossRatio// Access: public// Returns: BOOL// Qualifier: 根据丢失率随机生成一个数字,判断是否丢失,丢失则返回TRUE,否则返回 FALSE// Parameter: float lossRatio [0,1]//************************************BOOL lossInLossRatio(float lossRatio) {int lossBound = (int)(lossRatio * 100);int r = rand() % 101;if (r <= lossBound) {return TRUE;}return FALSE;}int main(int argc, char* argv[]){//加载套接字库(必须)WORD wVersionRequested;WSADATA wsaData;//套接字加载时错误提示int err;//版本 2.2wVersionRequested = MAKEWORD(2, 2);//加载 dll 文件 Scoket 库err = WSAStartup(wVersionRequested, &wsaData);if (err != 0) {//找不到 winsock.dllprintf("WSAStartup failed with error: %d\n", err);return 1;}if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){printf("Could not find a usable version of Winsock.dll\n");WSACleanup();}else {printf("The Winsock 2.2 dll was found okay\n");}SOCKET socketClient = socket(AF_INET, SOCK_DGRAM, 0);SOCKADDR_IN addrServer;addrServer.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);//addrServer.sin_addr.S_un.S_addr = inet_pton(AF_INET, SERVER_IP, 0);addrServer.sin_family = AF_INET;addrServer.sin_port = htons(SERVER_PORT);//接收缓冲区char buffer[BUFFER_LENGTH];ZeroMemory(buffer, sizeof(buffer));int len = sizeof(SOCKADDR);//为了测试与服务器的连接,可以使用 -time 命令从服务器端获得当前时间//使用 -testgbn [X] [Y] 测试 GBN 其中[X]表示数据包丢失概率//  [Y]表示 ACK 丢包概率printTips();int ret;int interval = 1;//收到数据包之后返回 ack 的间隔,默认为 1 表示每个都返回 ack,0 或者负数均表示所有的都不返回 ackchar cmd[128];float packetLossRatio = 0.2; //默认包丢失率 0.2float ackLossRatio = 0.2;  //默认 ACK 丢失率 0.2   //用时间作为随机种子,放在循环的最外面srand((unsigned)time(NULL));while (true) {gets_s(buffer);//scanf_s  函数解析//使用%s格式对数据解析时,缓冲长度必须大于字符串长度,否则不予解析  例子:%s,str,str.length//%d %f 后面的格式需匹配ret = sscanf_s(buffer, "%s%f%f", &cmd,strlen(cmd), &packetLossRatio, &ackLossRatio);//开始 GBN 测试,使用 GBN 协议实现 UDP 可靠文件传输//printf("%s\n",cmd);if (!strcmp(cmd, "-testgbn")) {//printf("%s\n", "Begin to test GBN protocol, please don't abort theprocess");printf("The loss ratio of packet is %.2f,the loss ratio of ackis %.2f\n", packetLossRatio, ackLossRatio);int waitCount = 0;int stage = 0;BOOL b;unsigned char u_code;//状态码unsigned short seq;//包的序列号unsigned short recvSeq;//接收窗口大小为 1,已确认的序列号unsigned short waitSeq;//等待的序列号//int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);//函数说明:sendto() 用来将数据由指定的socket 传给对方主机.//参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作.//参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send().//参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). //参数tolen 为sockaddr 的结果长度.    //int recvSend;recvSend = sendto(socketClient, "-testgbn", strlen("-testgbn") + 1, 0,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));if (recvSend == -1){printf("发送失败\n");//10013 - WSAEACCES   //权限被拒。尝试对套接字进行操作,但被禁止。若试图在sendto或WSASendTo中使用//一个广播地址,但是尚未用setsockopt和SO_BROADCAST这两个选项设置广播权限,//便会产生这类错误。printf("%d\n",WSAGetLastError());}while (true){//等待 server 回复设置 UDP 为阻塞模式recvSend =  recvfrom(socketClient, buffer, BUFFER_LENGTH, 0, (SOCKADDR*)&addrServer, &len);if (recvSend == SOCKET_ERROR){printf("接收失败\n");}switch (stage) {case 0://等待握手阶段u_code = (unsigned char)buffer[0];if ((unsigned char)buffer[0] == 205){printf("Ready for file transmission\n");buffer[0] = 200;buffer[1] = '\0';sendto(socketClient, buffer, 2, 0,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));stage = 1;recvSeq = 0;waitSeq = 1;}break;case 1://等待接收数据阶段seq = (unsigned short)buffer[0];//随机法模拟包是否丢失b = lossInLossRatio(packetLossRatio);if (b) {printf("The packet with a seq of %d loss\n", seq);continue;}printf("recv a packet with a seq of %d\n", seq);//如果是期待的包,正确接收,正常确认即可if (!(waitSeq - seq)) {++waitSeq;if (waitSeq == 21) {waitSeq = 1;}//输出数据printf("%s\n",&buffer[1]);buffer[0] = seq;recvSeq = seq;buffer[1] = '\0';}else {//如果当前一个包都没有收到,则等待 Seq 为 1 的数据包,不是则不返回 ACK(因为并没有上一个正确的 ACK)if (!recvSeq) {continue;}buffer[0] = recvSeq;buffer[1] = '\0';}b = lossInLossRatio(ackLossRatio);if (b) {printf("The  ack  of  %d  loss\n", (unsigned char)buffer[0]);continue;}sendto(socketClient, buffer, 2, 0,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));printf("send a ack of %d\n", (unsigned char)buffer[0]);break;}Sleep(500);}}sendto(socketClient, buffer, strlen(buffer) + 1, 0,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));ret = recvfrom(socketClient, buffer, BUFFER_LENGTH, 0, (SOCKADDR*)&addrServer,&len);//printf("%s\n", buffer);if (!strcmp(buffer, "Good bye!")) {break;}printTips();}//关闭套接字closesocket(socketClient);WSACleanup();system("pause");return 0;}


1 0
原创粉丝点击