目标:
创建两个进程,一个进程模拟用户,一个进程模拟服务器,用户能通过GBN和SR的方式向服务器发送文件,服务器也能给用户发送文件。
原理:
GBN即go back N步,当发生丢包并超时时,发送方直接重传滑动窗口内的所有内容,ack采取累计确认的模式,即一个序号为N的ack到达发送方时,发送方便能认为接收方收取到了序号小于N的所有分组。
SR为选择重传,接收方能缓存一定的失序分组,并向发送方响应对应的ack,等到最早未被确认的数据分组到达时,接收方便一骨碌依次接收数个分组,直到遇到了未到达的分组。言语匮乏,词不达意,对于GBN,SR的说明不清,还请自行了解。
实现思路:
GBN:
首先看发送方,本来想采取固定长度的数组作为缓存,但窗口大小并不一定,以及代码实现滑动较为复杂,因此思考后我采取了队列。发送方读取要传输的文件,先进行了分组,将分组按序存入队列q1(用于储存待发送的分组),发送时,经过发送的分组进入队列q2(用户储存已发送但未确认的分组),同时接收来自接收方的ack,如果ack的序号大于q2的队首分组的序号,则该队首出列,重复上述比较的过程,直到队首序号小于等于ack的序号(ack的序号为期望接收的序号,并没有经过确认)。
其次看接收方,接收方逻辑较为简单,因此GBN的接收方只需维护大小为一的接收缓存,失序到达的报文直接丢弃即可。接到一个分组时,接收方把它的序号和自己期望接收的序号对比,相等,将分组传给上层应用(这里仅仅是模拟,我只将其写入文件)则期望接受的序号加1,发送ack;不等,不管,丢弃该分组。
SR:
依然是先看发送方,与GBN大同小异,只是每个滑动窗口内的分组都有个独立的计时器,以用于发生超时时的重传。我为了偷懒,只用了一个计时器,即最先发送而未被确认的分组的。现在想起来其实并不和我队列的方式匹配,因为队列不弹出元素的话,只能访问队首与队尾,其他分组的计时器我就访问不了。同样的问题我在接收方也遇到了。举个例子,序号为1,3,4的分组到达,如果让我们作出此时接收方接收缓存的示意图,我们大概率会在1,3之间空出一个空位,而在代码层面我们不能像在纸上演示那样简单,首先,如果我们采取数组的方式作为接收缓存,那么如何确定分组的数组索引是多少呢?以及如何鉴别分组是已经接收过的,还是没接收过的分组。需要存到缓存中的,它们的区别,只有序号不一样。以我高三毕业再无寸进的语文水平我实在无法清楚的表达要阐述的问题,大家在纸上模拟各个分组存入缓存数组的过程就能发现问题所在。
代码:
注意,这里我将客户端的要传输的文件的名字起为了test.txt,服务器端的起名为data.txt,应该事先在当前文件夹下创建好这些文件,不然会出现文件打开错误。
运行环境为vs2022,下载了c++相关扩展。
客户端:
#include <stdio.h>
#include "stdlib.h"
#include <tchar.h>
#include <Windows.h>
#include <process.h>
#include <string.h>
#include<mutex>
#include <iostream>
#include <fstream>
#include <string>
#include<queue>
#include<map>
#pragma comment(lib,"Ws2_32.lib")
#define windowSize 5//最大窗口大小
#define clientPort 666//客户端的端口为666
#define serverPort 888//服务器端口为888
#define maxDataSize 1048//最大分组长度
#define TimerCount 2//倒计时的时间
#define orderCount 12//序号的大小
#define sendLossRate 0.3//模拟发送丢包率
#define canRepeat 2//可以重传的次数
#define ackLossRate 0.2//模拟接收丢包率
using namespace std;
queue<string> q1;//发送缓存
queue<string> q2;//已发送但未被接收
int Index = 0, len = sizeof(SOCKADDR),RepeatChance=canRepeat;//index用于读取文件的时候,给每个分组标序号,由于序号空间为12,因此index应该在0至11间循环
int lastAckedIndex = 0;
struct sockaddr_in addr;//发送目标的地址
SOCKET clientSocket;//客户端的套接字
sockaddr_in clientSocketAddr;//客户端的地址
double Timer=TimerCount;//计时器,为了偷懒我只设置了一个
BOOL InitSocket() {
//加载套接字库(必须)
WORD wVersionRequested;
WSADATA wsaData;
//套接字加载时错误提示
int err;
//版本 2.2
wVersionRequested = MAKEWORD(2, 2);
//加载 dll 文件 Scoket 库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
//找不到 winsock.dll
printf("加载 winsock 失败,错误代码为: %d\n", WSAGetLastError());
return FALSE;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("不能找到正确的 winsock 版本\n");
WSACleanup();
return FALSE;
}
clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (INVALID_SOCKET == clientSocket) {
printf("创建套接字失败,错误代码为:%d\n", WSAGetLastError());
return FALSE;
}
clientSocketAddr.sin_family = AF_INET;
clientSocketAddr.sin_port = htons(clientPort);
clientSocketAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (bind(clientSocket, (SOCKADDR*)&clientSocketAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
printf("绑定套接字失败\n");
return FALSE;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(serverPort);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int iMode = 1;//非阻塞
ioctlsocket(clientSocket, FIONBIO, (u_long FAR*)& iMode);
return TRUE;
}//初始化套接字库,绑定地址,以及设置接收方式为非阻塞
//其实是就是+1再模序号大小
void increaseIndex() {
if (Index == orderCount - 1) {
Index = 0;
}
else
Index++;
}
//从文件读入数据,并分组,标序号
void readFile() {
Index = 0;
ifstream file("test.txt", ios::in || ios::binary);
int count = 0;
string my;
char p;
while (true)
{
if (file.eof()) {
cout << "已全部读完\n";
break;
}
if (count == 1024) {
q1.push(to_string(Index) + "\r\n" + my);
count = 0;
increaseIndex();
my = "";
}
else {
file.read(&p, 1);
my = my + p;
count++;
}
}
if (!my.compare(""))
q1.push(to_string(Index) + "\r\n" + my.substr(0, my.length() - 1));
file.close();
}
//判断窗口大小,能不能继续发送分组
bool canSend() {
return windowSize > q2.size();
}
//从数据分组中提取出序号
int getIndexByString(string s) {
return atoi(s.substr(0, 2).c_str());//这里限制了index必须在100以内
}
//随机发生,用于模拟丢包
bool happenByPossibility(float f) {
int ran = rand() % 100;
if (ran > 100 * f) {
return true;
}
return false;
}
//使用gbn发送数据
void sendMessageGBN() {
RepeatChance = canRepeat;
int recvSize = -1, acked, base;
clock_t start, finish;
char* ack = new char[maxDataSize];
cout << q1.size()<<"个";
sendto(clientSocket, to_string(q1.size()).c_str(), sizeof(to_string(q1.size())), 0, (sockaddr*)&addr, sizeof(addr));//先说明要传送多少分组
while (q1.size() != 0 || q2.size() != 0) {
start = clock();
if (Timer <= 0) {//超时了,得重传
cout << "超时";
queue<string> tmp(q2);
while (tmp.size() > 0) {
sendto(clientSocket, tmp.front().c_str(), tmp.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
tmp.pop();
}
RepeatChance--;
if (RepeatChance == 0) {
cout << "\n接收方无响应\n";
return;
}
Timer = TimerCount;
}
while(canSend()) {
if (q1.size() != 0) {//还没发完
if (happenByPossibility(sendLossRate)) {
int nRet = sendto(clientSocket, q1.front().c_str(), q1.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
q2.push(q1.front());
q1.pop();//发送新的分组
}
else {
break;
}
}
recvSize = recvfrom(clientSocket, ack, maxDataSize, 0, (SOCKADDR*)&addr, &len);
if (recvSize < 0) {
cout << "未接到\n";
}
else {
RepeatChance = canRepeat;
acked = getIndexByString(ack);
cout << "已经确认" << acked;
base = getIndexByString(q2.front());
int i = base + q2.size() - orderCount;
if (acked <= i) {
acked += orderCount;
}
while (acked > getIndexByString(q2.front())) {
q2.pop();
Timer = TimerCount;
if (q2.size() == 0) {
break;
}
}
}
Sleep(100);
finish = clock();
Timer -= double(finish - start) / CLOCKS_PER_SEC;
cout << "\n剩余时间:" << Timer << endl;
}
}
//使用sr发送数据
void sendMessageSR() {
RepeatChance = canRepeat;
int recvSize = -1, acked, base;
map<int, bool> MAP;
clock_t start, finish;
char* ack = new char[1024];
cout << q1.size() << "个";
sendto(clientSocket, to_string(q1.size()).c_str(), sizeof(to_string(q1.size())), 0, (sockaddr*)&addr, sizeof(addr));//先说明要传送多少分组
while (q1.size() != 0 || q2.size() != 0) {
start = clock();
if (Timer <= 0) {//超时了,得重传
cout << "超时";
queue<string> tmp(q2);
while (tmp.size() > 0) {
sendto(clientSocket, tmp.front().c_str(), tmp.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
tmp.pop();
}
RepeatChance--;
if (RepeatChance == 0) {
cout << "\n接收方无响应\n";
return;
}
Timer = TimerCount;
}
while (canSend()) {
if (q1.size() != 0) {//还没发完
if (happenByPossibility(sendLossRate)) {
int nRet = sendto(clientSocket, q1.front().c_str(), q1.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
q2.push(q1.front());
MAP[getIndexByString(q1.front())] = false;
q1.pop();//发送新的分组
}
else {//全部分组都发送过了,但不一定都被确认
break;
}
}
recvSize = recvfrom(clientSocket, ack, maxDataSize, 0, (SOCKADDR*)&addr, &len);
if (recvSize < 0) {
cout << "未接到\n";
}
else {
RepeatChance = canRepeat;
acked = getIndexByString(ack);
cout << "已经确认" << acked;
MAP[acked] = true;
while (true)
{
if (q2.empty()) {
break;
}
base = getIndexByString(q2.front());
if (!MAP[base]) {
break;
}
q2.pop();//已经被确认的出队列
}
}
Sleep(100);
finish = clock();
Timer -= double(finish - start) / CLOCKS_PER_SEC;
cout << "\n剩余时间:" << Timer << endl;
}
}
//从数据分组中提取数据
char* getDataFromPackage(char* s) {
const char* del = "\r\n";
char* next;
strtok_s(s, del, &next);
return next + 2;
}
int main()
{
InitSocket();
string input;
char* recvData = new char[1024], * clear;
int recvSize = 0;
while (true)
{
Sleep(5000);
while ((recvSize = recvfrom(clientSocket, recvData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) > 0)
{
cout << "abort" << endl;//防止将上一次传输重传的报文认为是新数据分组,影响下面的判断
}
cout << "\n请输入指令:\n";
cin >> input;
int nRet = sendto(clientSocket, input.c_str(), input.length() + 1, 0, (SOCKADDR*)&addr, sizeof(addr));
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
while ((recvSize = recvfrom(clientSocket, recvData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
Sleep(100);
}
if (input.compare("-testgbn") == 0) {
if (recvData[0] == 'O'&& recvData[1] == 'k') {//说明服务器可以开始传输文件了
readFile();
sendMessageGBN();
}
}
else if (input.compare("-testsr") == 0) {
if (recvData[0] == 'O' && recvData[1] == 'k') {//说明服务器可以开始传输文件了
readFile();
sendMessageSR();
}
}
else if (input.compare("-url")==0) {
ofstream file("test.txt", ios::out || ios::binary || ios::trunc);
int packageNum = -1, currentAckedIndex = -1;
if (!file.fail()) {
string my = recvData;
//cout << my << "111\n";
packageNum = atoi(my.substr(0, 2).c_str());
cout << "\n共有" << packageNum << "个数据包\n";
while (packageNum > 0) {
char* newData = new char[1024];
while ((recvSize = recvfrom(clientSocket, newData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
Sleep(200);
}
if (getIndexByString(newData) == (currentAckedIndex + 1) % orderCount) {
char* data = getDataFromPackage(newData);
file.write(data, strlen(data) + 1);
currentAckedIndex++;
currentAckedIndex %= orderCount;
packageNum--;
if (happenByPossibility(ackLossRate)) {
int nRet = sendto(clientSocket, to_string((currentAckedIndex + 1) % orderCount).c_str(), sizeof(to_string((currentAckedIndex + 1) % orderCount)), 0, (SOCKADDR*)&addr, len);
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
else {
cout << "丢弃ack" << (currentAckedIndex + 1) % orderCount << endl;
}
}
}
cout << "接收完毕";
}
file.close();
}
else {
cout << recvData<<endl;
if (input.compare("-quit")==0) {
exit(0);
}
}
}
}
服务端:
#include <stdio.h>
#include "stdlib.h"
#include <tchar.h>
#include <Windows.h>
#include <process.h>
#include <string.h>
#include<mutex>
#include <iostream>
#include <fstream> //读写文件流
#include <string>
#include<queue>
#include <map>
using namespace std;
#pragma comment(lib,"Ws2_32.lib")
#define serverPort 888
#define clientPort 666
#define maxDataSize 1048
#define ackLossRate 0.2
#define windowSize 5
#define TimerCount 2
#define orderCount 12
#define sendLossRate 0.3
#define canRepeat 2
using namespace std;
int len=sizeof(SOCKADDR);
int Index = 0, RepeatChance = 2;
double Timer = TimerCount;
char* recvData = new char[1024];//可接收文件大小为1kB
SOCKET serverSocket;
SOCKADDR_IN serverSocketAddr,addr;
queue<string> q1;//发送缓存
queue<string> q2;//已发送但未被接收
BOOL InitSocket() {
//加载套接字库(必须)
WORD wVersionRequested;
WSADATA wsaData;
//套接字加载时错误提示
int err;
//版本 2.2
wVersionRequested = MAKEWORD(2, 2);
//加载 dll 文件 Scoket 库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
//找不到 winsock.dll
printf("加载 winsock 失败,错误代码为: %d\n", WSAGetLastError());
return FALSE;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("不能找到正确的 winsock 版本\n");
WSACleanup();
return FALSE;
}
serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (INVALID_SOCKET == serverSocket) {
printf("创建套接字失败,错误代码为:%d\n", WSAGetLastError());
return FALSE;
}
serverSocketAddr.sin_family = AF_INET;
serverSocketAddr.sin_port = htons(serverPort);
serverSocketAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");;
if (bind(serverSocket, (SOCKADDR*)&serverSocketAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
printf("绑定套接字失败\n");
return FALSE;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(clientPort);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int iMode = 1;//非阻塞
ioctlsocket(serverSocket, FIONBIO, (u_long FAR*) & iMode);
return TRUE;
}
int getIndexByString(string s) {
return atoi(s.substr(0, 2).c_str());//这里限制了index必须在100以内
}
char* getDataFromPackage(char *s) {
const char* del = "\r\n";
char* next;
strtok_s(s, del, &next);
return next+2;
}
bool happenByPossibility(float f) {
int ran = rand() % 100;
if (ran > 100 * f) {
return true;
}
return false;
}
void increaseIndex() {
if (Index == orderCount - 1) {
Index = 0;
}
else
Index++;
}
bool canSend() {
return windowSize > q2.size();
}
void readFile() {
Index = 0;
ifstream file("data.txt", ios::in || ios::binary);
int count = 0;
string my;
char p;
while (true)
{
if (file.eof()) {
cout << "已全部读完\n";
break;
}
if (count == 1024) {
q1.push(to_string(Index) + "\r\n" + my);
count = 0;
increaseIndex();
my = "";
}
else {
file.read(&p, 1);
my = my + p;
count++;
}
}
if (!my.compare(""))
q1.push(to_string(Index) + "\r\n" + my.substr(0, my.length() - 1));
file.close();
}
void sendMessageGBN() {
RepeatChance = canRepeat;
int recvSize = -1, acked, base;
clock_t start, finish;
char* ack = new char[maxDataSize ];
cout << q1.size() << "个";
sendto(serverSocket, to_string(q1.size()).c_str(), to_string(q1.size()).length()+1, 0, (sockaddr*)&addr, sizeof(addr));//先说明要传送多少分组
while (q1.size() != 0 || q2.size() != 0) {
start = clock();
if (Timer <= 0) {//超时了,得重传
RepeatChance--;
if (RepeatChance == 0) {
cout << "\n接收方无响应\n";
return;
}
cout << "超时";
queue<string> tmp(q2);
while (tmp.size() > 0) {
sendto(serverSocket, tmp.front().c_str(), tmp.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
tmp.pop();
}
Timer = TimerCount;
}
while (canSend()) {
if (q1.size() != 0) {//还没发完
if (happenByPossibility(sendLossRate)) {
int nRet = sendto(serverSocket, q1.front().c_str(), q1.front().length() + 1, 0, (sockaddr*)&addr, sizeof(addr));
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
q2.push(q1.front());
q1.pop();//发送新的分组
}
else {
break;
}
}
recvSize = recvfrom(serverSocket, ack, maxDataSize, 0, (SOCKADDR*)&addr, &len);
if (recvSize < 0) {
cout << "未接到\n";
}
else {
RepeatChance = canRepeat;
acked = getIndexByString(ack);
cout << "已经确认" << acked;
base = getIndexByString(q2.front());
int i = base + q2.size() - orderCount;
if (acked <= i) {
acked += orderCount;
}
while (acked > getIndexByString(q2.front())) {
q2.pop();
Timer = TimerCount;
if (q2.size() == 0) {
break;
}
}
}
Sleep(100);
finish = clock();
Timer -= double(finish - start) / CLOCKS_PER_SEC;
cout << "\n剩余时间:" << Timer << endl;
}
}
//处理用户发送的请求
void solveRequest() {
int recvSize = -1;
while (true)
{
if ((recvSize = recvfrom(serverSocket, recvData, 1024, 0, (SOCKADDR*)&addr, &len))<0){
cout << ".";
Sleep(200);
continue;
}
cout << '!';
if (strcmp(recvData, "-time")==0) {
SYSTEMTIME st;
GetLocalTime(&st);
string time;
time = to_string(st.wYear) + "-" + to_string(st.wMonth) + "-" + to_string(st.wDay) + "-" + to_string(st.wHour) + "-" + to_string(st.wMinute) + "-" + to_string(st.wSecond);\
sendto(serverSocket, time.c_str(), sizeof(time), 0, (SOCKADDR*)&addr, len);
}
else if (strcmp(recvData, "-quit") == 0) {
sendto(serverSocket, "goodbye", sizeof("goodbye"), 0, (SOCKADDR*)&addr, len);
}
else if (strcmp(recvData, "-testgbn") == 0) {
ofstream file("data.txt", ios::out || ios::binary||ios::trunc);
int packageNum = -1, currentAckedIndex = -1;
if (!file.fail()) {
sendto(serverSocket, "Ok", sizeof("Ok"), 0, (SOCKADDR*)&addr, len);
while ((recvSize = recvfrom(serverSocket, recvData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
cout << ".";
Sleep(200);
}
string my = recvData;
packageNum = atoi(my.substr(0, 2).c_str());
cout <<"\n共有" << packageNum<<"个数据包\n";
}
while (packageNum > 0) {
char* newData = new char[1024];
while ((recvSize = recvfrom(serverSocket, newData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
Sleep(200);
}
if (getIndexByString(newData) == (currentAckedIndex + 1) % orderCount) {
char *data = getDataFromPackage(newData);
file.write(data, strlen(data)+1);
currentAckedIndex++;
currentAckedIndex %= orderCount;
packageNum--;
cout << "收到" << currentAckedIndex << endl;
if (happenByPossibility(ackLossRate)) {
int nRet = sendto(serverSocket, to_string((currentAckedIndex + 1) % orderCount).c_str(), sizeof(to_string((currentAckedIndex + 1) % orderCount)), 0, (SOCKADDR*)&addr, len);
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
else {
cout << "丢弃" << (currentAckedIndex + 1) % orderCount<<endl;
}
}
}
cout << "接收完毕";
file.close();
}
else if (strcmp(recvData, "-testsr") == 0) {
ofstream file("data.txt", ios::out || ios::binary || ios::trunc);
int packageNum = -1, base=0,baseOrder=0;
string buff[windowSize]={""};
if (!file.fail()) {
sendto(serverSocket, "Ok", sizeof("Ok"), 0, (SOCKADDR*)&addr, len);
while ((recvSize = recvfrom(serverSocket, recvData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
cout << ".";
Sleep(200);
}
string my = recvData;
packageNum = atoi(my.substr(0, 2).c_str());
cout << "\n共有" << packageNum << "个数据包\n";
}
while (packageNum > 0) {
char* newData = new char[1024];
while ((recvSize = recvfrom(serverSocket, newData, maxDataSize, 0, (SOCKADDR*)&addr, &len)) < 0) {
Sleep(200);
}
int orderRecv = getIndexByString(newData);
char* data = getDataFromPackage(newData);
cout <<"\n收到分组:"<< orderRecv << endl;
cout << "最先未收到分组:" << base << endl;
if (baseOrder > orderCount-windowSize&&orderRecv<windowSize-1) {
buff[(orderRecv+orderCount) % windowSize] = data;
}
else if(baseOrder > orderRecv) {
}
else {
buff[orderRecv % windowSize] = data;
}
if (happenByPossibility(ackLossRate)) {
int nRet = sendto(serverSocket, to_string(orderRecv).c_str(), sizeof(to_string(orderRecv)), 0, (SOCKADDR*)&addr, len);
if (nRet == SOCKET_ERROR) {
cout << "sendto Error " << WSAGetLastError() << endl;
break;
}
}
else {
cout << "丢弃ack" << orderRecv << endl;
}
while (buff[base].length()!=0)
{
file.write(buff[base].c_str(), buff[base].length() + 1);
buff[base] = "";
base = ++base % windowSize;
baseOrder = ++baseOrder % orderCount;
packageNum--;
}
}
cout << "接收完毕";
file.close();
}
else if (strcmp(recvData, "-url") == 0) {
readFile();
sendMessageGBN();
}
else {
sendto(serverSocket, recvData, strlen(recvData)+1, 0, (SOCKADDR*)&addr, len);
}
}
}
int main()
{
InitSocket();
solveRequest();
}