计算机网络之GBN协议

客户端程序:

//#include "stdafx.h" //创建 VS 项目包含的预编译头文件
#include 
#include 
#include 
#include 
#include
#include
#include  
#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 的 ack
int curSeq;//当前数据包的 seq
int curAck;//当前等待确认的 ack
int 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.2
	wVersionRequested = MAKEWORD(2, 2);
	//加载 dll 文件 Scoket 库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		//找不到 winsock.dll
		printf("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) {
		//非阻塞接收,若没有收到数据,返回值为-1
		recvSize =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,准备接收200
					break;
				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,计数器+1
					recvSize = recvfrom(sockServer, buffer, BUFFER_LENGTH, 0, ((SOCKADDR*)&addrClient), &length);
					if (recvSize < 0) {
						waitCount++;
						//20 次等待 ack 则超时重传
						if (waitCount > 20)
						{
							timeoutHandler();//超时重传
							waitCount = 0;
						}
					}
					else {
						//收到 ack
						ackHandler(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 
#include
#include 
#include 
#include 

#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.2
	wVersionRequested = MAKEWORD(2, 2);
	//加载 dll 文件 Scoket 库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		//找不到 winsock.dll
		printf("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 或者负数均表示所有的都不返回 ack
	char cmd[128];
	float packetLossRatio = 0.2; //默认包丢失率 0.2
	float 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 the	process");
			printf("The loss ratio of packet is %.2f,the loss ratio of ack	is %.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;
}


你可能感兴趣的:(计算机网络,c++)