客户端程序:
//#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;
}