可靠数据传输协议实验
可靠数据传输协议Rdt
一、实验要求
1.1 实验目标和内容
1.1.1 实验目的
通过该实验了解和掌握运输层可靠数据传输原理以及具体实现方法。
1.1.2 实验环境
● 语言工具:基于 C++语言实现。
● 操作系统:Windows、Linux 操作系统。
● 其它要求:基于模拟网络环境 API 实现。
1.1.3 实验要求
● 可靠运输层协议实验只考虑单向传输,即:只有发送方发生数据报文,接收方仅仅接收报文并给出确认报文。
● 要求实现具体协议时,指定编码报文序号的二进制位数(例如 3 位二进制编码报文序号)以及窗口大小(例如大小为 4),报文段序号必须按照指定的二进制位数进行编码。
● 代码实现不需要基于 Socket API,不需要利用多线程,不需要任何 UI 界面。
● 提交实验设计报告和源代码;实验设计报告必须按照实验报告模板完成,源代码必须加详细注释。
● 代码编译运行成功后,运行给实验指导老师或者助教检查。
1.1.4 实验内容
本实验包括三个级别的内容,具体包括:
● 实现基于 GBN 的可靠传输协议,分值为 50%。
● 实现基于 SR 的可靠传输协议,分值为 30%。
● 在实现 GBN 协议的基础上,根据 TCP 的可靠数据传输机制实现一个简化版的 TCP协议,分值 20%,要求:
● 报文段格式、接收方缓冲区大小和 GBN 协议一样保持不变;
● 报文段序号按照报文段为单位进行编号;
● 单一的超时计时器,不需要估算 RTT 动态调整定时器 Timeout 参数;
● 支持快速重传和超时重传,重传时只重传最早发送且没被确认的报文段;
● 确认号为收到的最后一个报文段序号;
● 不考虑流量控制、拥塞控制
1.2 检查表
二、模拟网络环境介绍
由于需要模拟真实网络环境下实际可能发生的丢包、报文损坏的情况,因此开发了一个模拟的网络环境。模拟网络环境模拟实现了应用层和网络层,而需要和学生实现的运输层Rdt 协议协同工作完成数据的可靠传输
2.1 模拟网络环境功能
模拟的网络环境实现了以下功能:
● 应用层的数据向下递交给发送方运输层 Rdt 协议;
● 接收方运输层 Rdt 协议收到差错检测无误的报文后向上层应用层递交;
● 将发送方运输层 Rdt 协议准备好的报文通过网络层递交给接收方,在递交过程中按一定的概率会产生丢包、报文损坏;
● 定时器的启动、关闭、定时器 Timeout 后通知发送方运输层 Rdt 协议
2.2 模拟网络环境架构
图 1 给出了模拟网络环境架构和学生实现的 Rdt 协议的之间的关系。二者之间需要协同工作。蓝色背景部分为模拟网络环境,橙色背景部分为 Rdt 协议的发送方(RdtSender)和接收方(RdtReceiver)。红色箭头表示 RdtSender 和 RdtReceiver 调用模拟网络环境的函数,黑色箭头表示模拟环境调用 RdtSender 和 RdtReceiver 的函数。
模拟网络环境实现了以下函数以供 Rdt 协议调用:delivertoAppLayer(Message) 、sendToNetworkLayer(Packet) 、startTimer(int seqNum)、stopTimer(int seqNum),这些函数功能将在 2.3 节介绍。
Rdt 协议的发送方 RdtSender 和 Rdt 协议的接收方则是学生需要实现的功能,它们共同实现了 Rdt 协议。其中 RdtSender 必须实现三个函数:send(Message)、receive(Packet)、timeoutHandler(int seqNum);RdtReceiver 则必须实现一个函数:receive(Packet),这些函数的功能将在 2.3 节介绍。
2.3 模拟网络环境与学生实现的代码之间的调用关系
模拟网络环境模拟实现了应用层和网络层,而需要和学生实现的运输层 Rdt 协议协同工作完成数据的可靠传输。从应用层有数据到来开始,它们之间的调用关系如下所述:
① 模拟网络环境模拟产生应用层数据,调用 Rtdsender 的 Send(Message)方法;
② RtdSender 的 Send(Message)方法调用模拟网络环境的 sendToNetworkLayer(Packet)方法,将数据发送到模拟网络环境的网络层;
③ RtdSender 的 Send(Message)方法调用模拟网络环境的 startTimer( )方法启动定时器;
④ 模拟网络环境调用 RdtReceiver 的 receive(Packet)方法将数据交给 RdtReceiver;
⑤ 如果校验正确,RdtReceiver 调用模拟网络环境的 delivertoAppLayer(Message)方法将数据向上递交给应用层;
⑥ RdtReceiver 调用模拟网络环境的 sendToNetworkLayer(Packet)方法发送确认;
⑦ 模拟网络环境调用 RtdSender 的 receive(Packet)方法递交确认给 RtdSender;
⑧ 如果确认正确,RtdSender 调用模拟网络环境的 stopTimer 方法关闭定时器;
⑨ 如果确认不正确,RtdSender 调用模拟网络环境的 startTimer 方法重启定时器;
⑩ 如果定时器超时,模拟网络环境调用 RtdSender 的 timeoutHandler( )方法。
三、数据结构、接口定义与模拟网络环境 API 介绍
3.1 数据结构定义
DataStructure.h 头文件里定义了应用层消息 Message 和运输层 Packet 类,同时定义了基本的参数,具体如下:
**(1)Configuration:**配置类
struct Configuration{
static constint PAYLOAD_SIZE = 21;//定义各层协议Payload数据的大小(字节为单位)
static constint TIME_OUT =20;//定时器时间
};
**(2)Message:**第五层应用层消息
struct Message {
char data[Configuration::PAYLOAD_SIZE]; //payload
Message();
Message(const Message &msg);
Message& operator=(const Message &msg);
~Message();
void print();
};
**(3)Packet:**第四层运输层报文段
struct Packet {
int seqnum; //序号
int acknum; //确认号
int checksum; //校验和
char payload[Configuration::PAYLOAD_SIZE]; //payload
Packet();
Packet(const Packet &pkt);
Packet &operator=(const Packet &pkt);
bool operator==(const Packet &pkt) const;
~Packet();
void print();
};
3.2 RdtSender 和 RdtReceiver 接口定义
在头文件 RdtSender.h 和 RdtReceiver.h 里分别定义 Rdt 协议发送方和接收方必须实现的接口,是通过抽象类 RdtSender 和 RdtReceiver 进行接口的定义,具体分别如下:
(1)RdtSender:
//定义RdtSender抽象类,规定了必须实现的三个接口方法
//具体的子类比如StopWaitRdtSender、GBNRdtSender必须给出这三个方法的具体实现
//只考虑单向传输,即发送方只发送数据和接受确认
struct RdtSender {
//发送应用层下来的Message,由NetworkService调用。
//如果发送方成功地将Message发送到网络层,返回true;
//如果因为发送方处于等待确认状态或发送窗口已满而拒绝发送Message,则返回false
virtual bool send(Message &message) = 0;
//接受确认Ack,将被NetworkService调用
virtual void receive(Packet &ackPkt) = 0;
//Timeout handler,将被NetworkService调用
virtual void timeoutHandler(int seqNum) = 0;
//返回RdtSender是否处于等待状态,如果发送方正等待确认或者发送窗口已满,返回true
virtual bool getWaitingState() = 0;
};
这里需要特别说明的是:
(1)timeoutHandler 方法的参数 seqNum 为和该定时器关联的 Packet 的序号。虽然对于StopWait、GBN、简化版的 TCP 协议只需要一个定时器,但是该函数还是需要一个序号参数,只不过该序号是最早发出但没有被确认的 Packet 的序号。但是如果实现 SR 协议,那么该参数就有具体的意义了:它指明了是哪个 Packet 的定时器超时了。
(2)getWaitingState 函数返回 RdtSender 是否处于等待状态,对于具体的 Rdt 协议,是否处于等待状态具有不同的含义:例如对于 StopWait 协议,当发送方等待上层发送的 Packet的确认时,getWaitingState 函数应该返回 true;对于 GBN 协议,当发送方的发送窗口满了时,getWaitingState 函数应该返回 true。定义这个接口方法的原因是模拟网络环境需要调用RdtSender 的这个方法来判断是否需要将应用层下来的数据递交给 Rdt,这样学生实现RdtSender 时不需要在内部维护一个 Packet 队列了,因为当 getWaitingState 返回 true 时,应用层不会有数据下来。
(2)RdtReceiver:
//定义RdtReceiver抽象类,规定了必须实现的一个接口方法
//具体的子类比如StopWaitRdtReceiver、GBNRdtReceiver必须给出这一个方法的具体实现
//只考虑单向传输,即接收方只接收数据
struct RdtReceiver {
virtual void receive(Packet &packet) = 0; //接收报文,将被NetworkService调用
};
RdtReceiver 的接口定义就简单的多,这里不再解释。具体的 Rdt 协议实现类必须继承 这二个抽象类,这样就保证了不同的 Rdt 协议接口一致性。否则具体 Rdt 协议实现类的对象无法注入到模拟网络环境一起协同工作
3.3 模拟网络环境 API 接口定义
模拟网络环境 API 接口定义了学生实现的具体 Rdt 协议实现类可以调用的函数,具体定义在 NetworkService.h 头文件中定义:
//定义NetworkService抽象类,规定了学生实现的RdtSender和RdtReceiver可以调用的的接口方法
struct NetworkService {
//发送方启动定时器,由RdtSender调用
virtual void startTimer(RandomEventTarget target, int timeOut,int seqNum) = 0;
//发送方停止定时器,由RdtSender调用
virtual void stopTimer(RandomEventTarget target,int seqNum) = 0;
//将数据包发送到网络层,由RdtSender或RdtReceiver调用
virtual void sendToNetworkLayer(RandomEventTarget target, Packet pkt) = 0;
//将数据包向上递交到应用层,由RdtReceiver调用
virtual void delivertoAppLayer(RandomEventTarget target, Message msg) = 0;
//初始化网络环境,在main里调用
virtual void init() = 0;
//启动网络环境,在main里调用
virtual void start() = 0;
//注入具体的发送方对象,在main里调用
virtual void setRtdSender(RdtSender *ps) = 0;
//设置具体的接收对象,在main里调用
virtual void setRtdReceiver(RdtReceiver *ps) = 0;
//设置输入文件路径
virtual void setInputFile(const char *ifile) = 0;
//设置输出文件路径
virtual void setOutputFile(const char *ofile) = 0;
//设置运行模式,0:VERBOSE模式,1:安静模式
virtual void setRunMode(int mode = 0) = 0;
};
这里需要特别说明的是:
(1)RandomEventTarget 是定义的枚举类型(具体定义在 3.4 里说明),用来标识发送方和接收方。 当调用 startTimer 和 stopTimer 时 , 该参数设 为 SENDER ; 当调用sendToNetworkLayer 方法时,该参数设为对方,即如果是发送方调用 sendToNetworkLayer 方法时发送数据报文,该参数设为 RECEIVER;如是接收方调用 sendToNetworkLayer 方法时发送确认报文,该参数设为 SENDER;当接收方调用 delivertoAppLayer 方法时,该参数设为 RECEIVER
(2)startTimer 和 stopTimer 方法都需要设置 seqNum 参数,该参数应该是和该定时器相关的 Packet 序号。即使是 GBN 和简化版 TCP 这些协议,也要设置该参数(应为最早发出但未确认的 Packet 序号)。另外重新启动一个定时器前,一定要先关闭该定时器(要注意 seqNum 参数的一致性),否则模拟网络环境会提示“试图启动一个已启动的定时器”。
(3)setInputFile 和 setOutputFile 是增加的二个接口函数,设置输入文件和输出文件的路径。这是为了验证协议的正确性而添加的:模拟网络环境的发送方会读取文件,构造应用层的 Message,调用 RdtSender 的 send 方法将 Message 发送到接收方,接收 RdtReceiver 再将正确收到的报文调用 delivertoAppLayer 方法交给接收方模拟网络环境的应用层,最后写入到输出文件。如果输入文件和输出文件内容一样,说明协议工作正确。测试用的输入文件请使用发布的 input.txt 文件,如果协议工作正确,程序会产生输出文件,并且输出文件和输入文件的内容一致。
(4)setRunMode 函数也是增加的接口函数,用于设置网络模拟环境的运行模式。如果设置 mode=0(也是该函数的缺省参数),则为 Verbose 模式,网络模拟环境会输出很多模拟环境的运行信息,可以帮助学生观察协议的工作过程,特别是协议实现有问题时,可以帮助
分析协议出现的问题;如果设置为 mode=1,则为 Silence 模式,这是会关闭掉模拟环境输出的运行信息,而控制台只会输出学生协议实现代码里打印的信息。
3.4 其它定义
在 RandomEventEnum.h 头文件里定义了枚举类型,用来标识发送方和接收方。其定义为:
/* 定义随机事件的目标*/
enum RandomEventTarget {
SENDER, //数据发送方
RECEIVER //数据接收方
};
在 Tool.h 头文件里,定义了可以使用的工具接口,其定义为:
struct Tool{
/* 打印Packet的信息*/
virtual void printPacket(const char * description, const Packet &packet) = 0;
/*计算一个Packet的校验和*/
virtual int calculateCheckSum(const Packet &packet) = 0;
/*产生一个均匀分布的[0-1]间的随机数*/
virtual double random() = 0;
};
其中 void printPacket(const char * description, const Packet &packet)函数可以用来打印调试信息, 第一 个参数为描述性字符串, 第二个 参 为要 打印 输出 的Packet; int calculateCheckSum(const Packet &packet)函数计算给定 Packet 的校验和;double random()函
数是模拟网络环境所需的。这里要特别说明的是校验和的计算请调用 Tool 接口定义的方法。
在 Global.h 里声明了二个全局指针,分别指向实现了 Tool 接口和 NetworkService 接口的实例,学生代码中通过这二个指针调用工具接口提供的函数和模拟网络环境提供的函数。由于这二个指针没有封装在智能指针里,学生需要在 main 函数结束前 delete 这二个指针。
四、停止等待协议(Rdt3.0)实现示例
4.1 开发环境配置
(1)link 模拟网络环境静态库:
模拟网络环境已经被编译成静态库的 lib 文件(文件名为 netsimlib.lib),因此学生的代码工程编译时需要 link 这个 lib 文件。以 Windows 平台下的 VS2017 为例,可以有很多办法link 一个自定义的静态库 lib 文件,在 StopWait 示例工程中,是在 stdAfx.h 头文件里加上如下的编译预处理指令将静态库链接在一起:
#pragma comment (lib,"D:\\DotNetProject\\VS2017\\Rdt\\Debug\\netsimlib.lib")
(2)需要包含的头文件:
在学生代码工程里,需要包含以下头文件:
● DataStructure.h
● Global.h
● NetworkService.h
● RandomEventEnum.h
● RdtReceiver.h
● RdtSender.h
● Tool.h
另外还需要如下常规的头文件:
● stdio.h
● string.h
● iostream
4.2 停止等待协议发送方实现
停止等待协议发送方是通过类 StopWaitRdtSender 实现,而 StopWaitRdtSender 继承了抽象类 RdtSender,具体定义如下:
class StopWaitRdtSender :public RdtSender{
private:
int expectSequenceNumberSend; // 下一个发送序号
bool waitingState; // 是否处于等待Ack的状态
Packet packetWaitingAck; //已发送并等待Ack的数据包
public:
bool getWaitingState();
//发送应用层下来的Message,由NetworkServiceSimulator调用,
//如果发送方成功地将Message发送到网络层,返回true;
//如果因为发送方处于等待正确确认状态而拒绝发送Message,则返回false
bool send(Message &message);
//接受确认Ack,将被NetworkServiceSimulator调用
void receive(Packet &ackPkt);
//Timeout handler,将被NetworkServiceSimulator调用
void timeoutHandler(int seqNum);
public:
StopWaitRdtSender();
virtual ~StopWaitRdtSender();
};
StopWaitRdtSender 类的函数实现具体为:
//构造函数
StopWaitRdtSender::StopWaitRdtSender():expectSequenceNumberSend(0),waitingState(false){
}
StopWaitRdtSender::~StopWaitRdtSender(){
}
bool StopWaitRdtSender::getWaitingState() {
return waitingState;
}
bool StopWaitRdtSender::send(Message &message) {
if (this->waitingState) { //发送方处于等待确认状态
return false;
}
this->packetWaitingAck.acknum = -1; //忽略该字段,发送方的packet用不到acknum,ACK报文段才需要
this->packetWaitingAck.seqnum = this->expectSequenceNumberSend;
this->packetWaitingAck.checksum = 0;
memcpy(this->packetWaitingAck.payload, message.data, sizeof(message.data));
this->packetWaitingAck.checksum = pUtils->calculateCheckSum(this->packetWaitingAck);
pUtils->printPacket("发送方发送报文", this->packetWaitingAck);
//启动发送方定时器
pns->startTimer(SENDER, Configuration::TIME_OUT,this->packetWaitingAck.seqnum);
//调用模拟网络环境的sendToNetworkLayer,通过网络层发送到对方
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
//进入等待状态
this->waitingState = true;
return true;
}
void StopWaitRdtSender::receive(Packet &ackPkt) {
//如果发送方处于等待ack的状态,作如下处理;否则什么都不做
if (this->waitingState == true) {
//检查校验和是否正确
int checkSum = pUtils->calculateCheckSum(ackPkt);
//如果校验和正确,并且确认序号=发送方已发送并等待确认的数据包序号
if (checkSum == ackPkt.checksum && ackPkt.acknum == this->packetWaitingAck.seqnum) {
//下一个发送序号在0-1之间切换
this->expectSequenceNumberSend = 1 - this->expectSequenceNumberSend;
this->waitingState = false;
pUtils->printPacket("发送方正确收到确认", ackPkt);
//关闭定时器
pns->stopTimer(SENDER, this->packetWaitingAck.seqnum);
}
else {
pUtils->printPacket("发送方没有正确收到确认,重发上次发送的报文", this->packetWaitingAck);
//首先关闭定时器
pns->stopTimer(SENDER, this->packetWaitingAck.seqnum);
//重新启动发送方定时器
pns->startTimer(SENDER, Configuration::TIME_OUT, this->packetWaitingAck.seqnum);
//重新发送数据包
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
}
}
}
void StopWaitRdtSender::timeoutHandler(intseqNum) {
//唯一一个定时器,无需考虑seqNum
pUtils->printPacket("发送方定时器时间到,重发上次发送的报文", this->packetWaitingAck);
//首先关闭定时器
pns->stopTimer(SENDER,seqNum);
//重新启动发送方定时器
pns->startTimer(SENDER, Configuration::TIME_OUT,seqNum);
//重新发送数据包
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
}
4.3 停止等待协议接收方实现
停止等待协议发送方是通过类 StopWaitRdtReceiver 实现,而 StopWaitRdtReceiver 继承了抽象类 RdtReceiver,具体定义如下:
class StopWaitRdtReceiver :publicRdtReceiver{
private:
int expectSequenceNumberRcvd; // 期待收到的下一个报文序号
Packet lastAckPkt; //上次发送的确认报文
public:
StopWaitRdtReceiver();
virtual ~StopWaitRdtReceiver();
public:
void receive(Packet &packet); //接收报文,将被NetworkService调用
};
StopWaitRdtReceiver 类的函数实现具体为:
StopWaitRdtReceiver::StopWaitRdtReceiver():expectSequenceNumberRcvd(0){
//初始状态下,上次发送的确认包的确认序号为-1,
//使得当第一个接受的数据包出错时该确认报文的确认号为-1
lastAckPkt.acknum = -1;
lastAckPkt.checksum = 0;
lastAckPkt.seqnum = -1; //忽略该字段
for(int i = 0; i<Configuration::PAYLOAD_SIZE;i++){
lastAckPkt.payload[i] = '.';
}
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
}
StopWaitRdtReceiver::~StopWaitRdtReceiver(){
}
void StopWaitRdtReceiver::receive(Packet &packet) {
//检查校验和是否正确
int checkSum = pUtils->calculateCheckSum(packet);
//如果校验和正确,同时收到报文的序号等于接收方期待收到的报文序号一致
if (checkSum == packet.checksum&&this->expectSequenceNumberRcvd == packet.seqnum) {
pUtils->printPacket("接收方正确收到发送方的报文", packet);
//取出Message,向上递交给应用层
Message msg;
memcpy(msg.data, packet.payload, sizeof(packet.payload));
pns->delivertoAppLayer(RECEIVER, msg);
lastAckPkt.acknum = packet.seqnum; //确认序号等于收到的报文序号
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
pUtils->printPacket("接收方发送确认报文", lastAckPkt);
//调用模拟网络环境的sendToNetworkLayer,通过网络层发送确认报文到对方
pns->sendToNetworkLayer(SENDER, lastAckPkt);
//接收序号在0-1之间切换
this->expectSequenceNumberRcvd = 1 - this->expectSequenceNumberRcvd;
}
else {
if (checkSum != packet.checksum) {
pUtils->printPacket("接收方没有正确收到发送方的报文,数据校验错误", packet);
}
else {
pUtils->printPacket("接收方没有正确收到发送方的报文,报文序号不对", packet);
}
pUtils->printPacket("接收方重新发送上次的确认报文", lastAckPkt);
//调用模拟网络环境的sendToNetworkLayer,通过网络层发送上次的确认报文
pns->sendToNetworkLayer(SENDER, lastAckPkt);
}
}
4.4 启动模拟网络环境
模拟网络环境的启动在 main 函数里实现,具体代码为:
#include"stdafx.h"
#include"Global.h"
#include"RdtSender.h"
#include"RdtReceiver.h"
#include"StopWaitRdtSender.h"
#include"StopWaitRdtReceiver.h"
int main(int argc, char* argv[]){
//如果需要使用其它的Rdt协议,只需要实例化其他具体Rdt实现类的实例,
//如GBNRdtSender和GBNRdtSeceiver
RdtSender *ps = newStopWaitRdtSender();
RdtReceiver * pr = newStopWaitRdtReceiver();
pns->setRunMode(0); //VERBOS模式
//pns->setRunMode(1); //安静模式
pns->init();
pns->setRtdSender(ps);
pns->setRtdReceiver(pr);
pns->setInputFile("C:\\Users\\crackryan\\Desktop\\input.txt");
pns->setOutputFile("C:\\Users\\crackryan\\Desktop\\output.txt");
pns->start();
delete ps;
delete pr;
//指向唯一的工具类实例,只在main函数结束前delete
delete pUtils;
//指向唯一的模拟网络环境类实例,只在main函数结束前delete
delete pns;
return 0;
}
五、GBN协议的实现
5.1 GBN协议的原理
允许发送方发送多个分组而不需等待确认,但已发送但未确认的分组数不能超过N
-
发送方:
① 分组首部用k-比特字段表示序号(二进制)
② 已被传输但还未确认的分组的许可序号范围可以看作是一个在序号范围内大小为N的“窗口(window)”基序号(base):最早的未确认的分组序号
下一个序号(nextseqnum):下一个待发分组的序号①以被确认的分组
②已发送但未确认的分组
③未发送但可以被发送的分组
④ 大于等于的分组:不能发送累加确认:对序号n之前包括n在内的所有分组进行确认
ACK-only:只对正确按序到达的分组发送ACK
超时重发:为最早已发送但未确认的分组设置定时器(只需要一个定时器)
为什么要限制滑动窗口大小:为了流量控制 -
接收方:
失序分组或损坏分组:
① 丢弃 (不缓存) -> 接收方无缓存!
② 重发正确按序到达的最高序号分组的ACK
③ 每次发送的ACK一定是对正确按序到达的最高序号分组的确认
5.2 GBN协议发送方实现
stdafx.h
实现如下:
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include <fstream>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <math.h>
// TODO: 在此处引用程序需要的其他头文件
#pragma comment (lib,"E:\\GBN\\netsimlib.lib")
#include <iostream>
using namespace std;
//#pragma warning(disable:4482)
首先定义发送方的类 GBNSender
,实现 GBNSender.h
#ifndef STOP_WAIT_RDT_SENDER_H
#define STOP_WAIT_RDT_SENDER_H
#include "RdtSender.h"
#include "stdafx.h"
#include <vector>
class GBNSender :public RdtSender {
private:
int windowLength; // 发送窗口大小
int seqLength; // 序号对应的二进制数位数
int base; // 基序号:最早的未确认的分组编号
int nextseqnum; // 下一个发送序号
vector <Packet> sndpkt; // 发送方缓存Packet
bool waitingState; // 是否处于等待Ack的状态
Packet packetWaitingAck; // 已发送并等待Ack的数据包
public:
bool getWaitingState();
bool send(const Message& message); //发送应用层下来的Message,由NetworkServiceSimulator调用,如果发送方成功地将Message发送到网络层,返回true;如果因为发送方处于等待正确确认状态而拒绝发送Message,则返回false
void receive(const Packet& ackPkt); //接受确认Ack,将被NetworkServiceSimulator调用
void timeoutHandler(int seqNum); //Timeout handler,将被NetworkServiceSimulator调用
public:
GBNSender();
virtual ~GBNSender();
};
#endif
接着要根据FSM实现相应的成员函数,即编写文件 GBNSender.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "GBNSender.h"
//控制台颜色控制:滑动窗口中已发送的标记为黄色,未发送的标记为蓝色
void color(int x) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);//设置不同数字的颜色
}
//构造函数
GBNSender::GBNSender() :base(0), nextseqnum(0), waitingState(false), windowLength(4), seqLength(3) {
}
//析构函数
GBNSender::~GBNSender() {
}
//获取等待状态——GBN用等待状态衡量当前窗口是否已满,如果未满才可以接受应用层数据
bool GBNSender::getWaitingState() {
if (this->sndpkt.size() >= this->windowLength) {
this->waitingState = true;
}
else this->waitingState = false;
return this->waitingState;
}
//向接收方发送一个packet
bool GBNSender::send(const Message& message) {
if (this->getWaitingState()) {//窗口满,拒绝发送
return false;
}
this->packetWaitingAck.acknum = -1; //忽略该字段,发送方报文用不到
this->packetWaitingAck.seqnum = this->nextseqnum; //报文段序号nextseqnum
this->packetWaitingAck.checksum = 0; //初始化校验和
memcpy(this->packetWaitingAck.payload, message.data, sizeof(message.data)); //设置报文段内容
this->packetWaitingAck.checksum = pUtils->calculateCheckSum(this->packetWaitingAck);//设置校验和
this->sndpkt.push_back(this->packetWaitingAck);//发送方缓存窗口内的报文段
pUtils->printPacket("发送方发送报文", this->packetWaitingAck);
//只为base设置定时器,定时器超时之后发送sndpkt[base]~sndpkt[nextseqnum-1]
if (this->base == this->nextseqnum) {
pns->startTimer(SENDER, Configuration::TIME_OUT, this->packetWaitingAck.seqnum);
}
//注意序号的二进制位数的限制,所以要取余
this->nextseqnum = (this->nextseqnum + 1) % int(pow(2, this->seqLength));
//向网络层交付数据
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
return true;
}
//收到接收方的ACK
void GBNSender::receive(const Packet& ackPkt) {
//只考虑校验和正确的ACK,如果收到不正确的ACK不作任何处理(因为是累加确认)
int checksum = pUtils->calculateCheckSum(ackPkt);
//还需要考虑收到的ACK是否对应窗口中的编号,即ackPkt->acknum是否在[base,base+sndpkt.size()-1]内
int seqLen = int(pow(2, this->seqLength));
int offacknum = (ackPkt.acknum - this->base + seqLen) % seqLen;
if (checksum == ackPkt.checksum && offacknum < this->sndpkt.size()) {
//控制台可视化移动前滑动窗口
//将移动前滑动窗口移动信息写入日志文件
ofstream ofs;
ofs.open("log.txt", ios::app);
cout << "\n移动前滑动窗口为:[";
ofs << "\n移动前滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i< this->sndpkt.size()) color(224);
else color(159);
cout << (this->base + i) % seqLen;
ofs << (this->base + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]" << endl;
ofs << "]" << endl;
ofs.close();
//移动滑动窗口base=acknum+1,同时删除缓存中已经确认的packet
pUtils->printPacket("发送方正确收到确认", ackPkt);
//因为重启定时器前也需要关闭定时器,所以没必要判断,无脑关就行了,但是要注意关闭的定时器的序号!
pns->stopTimer(SENDER, this->base);
while (this->base != (ackPkt.acknum + 1) % seqLen) {
this->sndpkt.erase(this->sndpkt.begin(), this->sndpkt.begin() + 1);
this->base = (this->base + 1) % seqLen;
}
//控制台可视化移动后滑动窗口
//将移动后滑动窗口移动信息写入日志文件
ofs.open("log.txt", ios::app);
cout << "移动后滑动窗口为:[";
ofs << "移动后滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i < this->sndpkt.size()) color(224);
else color(159);
cout << (this->base + i) % seqLen;
ofs << (this->base + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]\n" << endl;
ofs << "]" << endl;
ofs.close();
//判断定时器处理
if (this->sndpkt.size()!=0) {//非空,仍需重启定时器
pns->startTimer(SENDER, Configuration::TIME_OUT, this->base);//以基准包的序号开启计时器
}
}
else if (checksum != ackPkt.acknum) {//ACK传输出错,不作任何处理
pUtils->printPacket("ACK数据校验错误", ackPkt);
}
else pUtils->printPacket("已收到过该ACK确认", ackPkt);
}
//序号为seqNum的定时器超时,重传缓存的内容
void GBNSender::timeoutHandler(int seqNum) {
pns->stopTimer(SENDER, seqNum);
pns->startTimer(SENDER, Configuration::TIME_OUT, seqNum);
//重发缓存的所有数据包
for (int i = 0; i < this->sndpkt.size(); ++i) {
pUtils->printPacket("发送方定时器超时,重发全部缓存报文", this->sndpkt[i]);
pns->sendToNetworkLayer(RECEIVER, this->sndpkt[i]);
}
}
5.3 GBN协议接收方实现
首先定义接收方的类 GBNReceiver
,即编写文件 GBNReceiver.h
:
#ifndef STOP_WAIT_RDT_RECEIVER_H
#define STOP_WAIT_RDT_RECEIVER_H
#include "RdtReceiver.h"
class GBNReceiver :public RdtReceiver {
private:
int expectedseqnum; //期望收到的packet序号
int seqLength; //序号二进制数位数
Packet lastAckPkt; //上次发送的ACK报文
public:
GBNReceiver();
virtual ~GBNReceiver();
public:
void receive(const Packet& packet); //接收报文,将被NetworkService调用
};
#endif
接着我们实现各个成员函数的定义,即编写文件 GBNReceiver.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "GBNReceiver.h"
//构造函数
GBNReceiver::GBNReceiver() :expectedseqnum(0), seqLength(3) {
//因为我们设计的GBN中序号从0开始,因此初始化的acknum=-1
lastAckPkt.acknum = -1; //初始状态下,上次发送的确认包的确认序号为-1,使得当第一个接受的数据包出错时该确认报文的确认号为-1
lastAckPkt.checksum = 0;
lastAckPkt.seqnum = -1; //忽略该字段
for (int i = 0; i < Configuration::PAYLOAD_SIZE; i++) {
lastAckPkt.payload[i] = '.';
}
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
}
//析构函数
GBNReceiver::~GBNReceiver() {
}
//接收到packet报文,反馈ACK
void GBNReceiver::receive(const Packet& packet) {
//检查校验和是否正确
int checkSum = pUtils->calculateCheckSum(packet);
//如果校验和正确,同时收到报文的序号等于接收方期待收到的报文序号一致
if (checkSum == packet.checksum && this->expectedseqnum == packet.seqnum) {
pUtils->printPacket("接收方正确收到发送方的报文", packet);
//取出Message,向上递交给应用层
Message msg;
memcpy(msg.data, packet.payload, sizeof(packet.payload));
pns->delivertoAppLayer(RECEIVER, msg);
lastAckPkt.acknum = packet.seqnum; //确认序号等于收到的报文序号
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
pUtils->printPacket("接收方发送确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送确认报文到对方
this->expectedseqnum = (this->expectedseqnum + 1) % int(pow(2, this->seqLength));//接收序号在0-1之间切换
}
else {
if (checkSum != packet.checksum) {
pUtils->printPacket("接收方没有正确收到发送方的报文,数据校验错误", packet);
}
else {
pUtils->printPacket("接收方没有正确收到发送方的报文,报文序号不对", packet);
}
pUtils->printPacket("接收方重新发送上次的确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送上次的确认报文
}
}
接收方基本是与Rdt3.0相同的
5.4 GBN主模块编写
//GBN.cpp:控制台程序入口点
#include "stdafx.h"
#include "Global.h"
#include "RdtSender.h"
#include "RdtReceiver.h"
#include "GBNSender.h"
#include "GBNReceiver.h"
int main(int argc, char* argv[]){
system("color F0");
//日志文件分隔头
ofstream ofs;
ofs.open("log.txt", ios::app);
ofs << "********************* 进行一次文件传输 *********************" << endl;
ofs.close();
RdtSender* ps = new GBNSender();
RdtReceiver* pr = new GBNReceiver();
// pns->setRunMode(0); //VERBOS模式
pns->setRunMode(1); //安静模式
pns->init();
pns->setRtdSender(ps);
pns->setRtdReceiver(pr);
pns->setInputFile("E:\\GBN\\input.txt");
pns->setOutputFile("E:\\GBN\\output.txt");
pns->start();
delete ps;
delete pr;
delete pUtils; //指向唯一的工具类实例,只在main函数结束前delete
delete pns; //指向唯一的模拟网络环境类实例,只在main函数结束前delete
return 0;
}
最终运行结果的部分截图如下:
六、SR协议的实现
6.1 SR协议原理
① 选择重传:解决GBN大量重传分组的问题
② 接收方逐个对所有正确收到(即使失序)的分组进行确认(不是累积确认)
③ 对接收到的(失序)分组进行缓存(GBN不缓存), 以便最后对上层进行有序递交
④ 发送方只重发怀疑丢失或损坏的分组:发送方为每一个没有收到ACK的分组设置定时器
⑤ 发送窗口:大小为N,范围**[sendbase, sendbase + N - 1],限制已发送但未被确认的分组数最多为N,sendbase前的分组都被确认
⑥ 接受窗口:大小为N,范围[recvbase, recvbase + N - 1]**,落在窗口内的序号都是期待收到的分组序号,recvbase前都是按序到达,已发出确认,且已递交给上层
注意上述粉色条的注释 “失序(已缓存)但未被确认” 应改为 “已收到但失序(已缓存)”
(1)发送方:
① 从上层收到数据:
如果下一个可用于该分组的序号在窗口内,则将数据打包并发送。否则要么缓存,要么拒绝上层
② 超时(n),序号为n的分组超时:
重传分组n,重置定时器
③ 收到 在 [sendbase,sendbase+N-1]范围内:
标记分组 n 为已接收
④ 如果n是发送窗口基序号sendbase:
则将窗口基序号前推到下一个未确认序号(因此,sendbase前的分组一定都被确认过了),如果不是窗口不动
⑤ 发送方可能会收到比sendbase还小的序号的分组确认:
这时什么都不做(因为比sendbase还早的分组都被确认)
为什么发送方会收到比sendbase还小的分组确认?——定时太短
例如:窗口位于sendbase-1时,序号为sendbase-1的分组定时器时间到还没收到ACK(sendbase-1),则发送方重发该分组。
刚重发,收到了Ack(sendbase-1),窗口前移到sendbase,窗口移动后,又收到了Ack(sendbase-1)
(2)接收方:
① 分组序号n在[rcvbase, rcvbase+N-1]范围内
(ⅰ)分组正确接收,发送n的确认ACK(n)(不管是否为重复分组及是否失序),如果分组是以前没收到过的:将其缓存
(ⅱ)如果该分组序号=recvbase,则将从该分组开始序号连续的分组一起交给上层,然后,窗口按向上交付的分组的数量向前移动
② 分组序号n 在 [rcvbase-N,rcvbase-1]范围内:
虽然曾经确认过,仍再次发送n的确认ACK(n),若不发确认,发送方窗口无法向前移动
③ 其他情况:忽略该分组
为什么接收方会收到[recvbase-N,recvbase - 1]范围内的分组?并且必须给出确认?
因为可能会丢失。假设接受方按序收到N个分组,向发送方发送确认后接受窗口向前移动N位,但是确认分组全部丢失,导致发送方重发,发送方最多只能发N个,因此接收方会收到[recvbase-N,recvbase - 1]范围内的分组,接收方这时必须给出确认,否则发送方窗口无法向前移动
为什么接收方收到比recvbase-N更早的分组后不用发确认了?
因为比recvbase-N更早的分组(如recvbase-N-1 ),发送方一定收到确认了。
当接受窗口位于recvbase时,意味着接收方一定按序收到了从[recvbase-N, recvbase - 1]的分组,这意味着发送方窗口一定到了recvbase-N,因此发送方一定收到了比recvbase-N更早的确认
6.2 SR协议发送方实现
stdafx.h
:
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include <fstream>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <math.h>
// TODO: 在此处引用程序需要的其他头文件
#pragma comment (lib,"E:\\SR\\netsimlib.lib")
#include <iostream>
using namespace std;
//#pragma warning(disable:4482)
//控制台颜色控制:滑动窗口中已发送的标记为黄色,未发送的标记为蓝色,已确认的标记为绿色
inline void color(int x) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);//设置不同数字的颜色
}
SRSender.h
:
#ifndef STOP_WAIT_RDT_SENDER_H
#define STOP_WAIT_RDT_SENDER_H
#include "RdtSender.h"
#include "stdafx.h"
#include <vector>
//SR协议中我们不仅需要缓存分组,还需要缓存分组的状态(是否被确认),不妨将这两个量封装成一个类
struct pktWithState {//带确认状态的packet
bool ack; //是否收到ack确认
Packet pkt; //相应packet
};
class SRSender :public RdtSender {
private:
int windowLength; //发送方窗口大小
int seqLength; // 序号对应的二进制数位数
int sendbase; //发送方基序号
int nextseqnum; //下一个将要发送的分组的序号
vector<pktWithState> sndpkt; //缓存带状态分组
bool waitingState; // 是否处于等待Ack的状态
Packet packetWaitingAck; // 已发送并等待Ack的数据包
public:
bool getWaitingState();
bool send(const Message& message); //发送应用层下来的Message,由NetworkServiceSimulator调用,如果发送方成功地将Message发送到网络层,返回true;如果因为发送方处于等待正确确认状态而拒绝发送Message,则返回false
void receive(const Packet& ackPkt); //接受确认Ack,将被NetworkServiceSimulator调用
void timeoutHandler(int seqNum); //Timeout handler,将被NetworkServiceSimulator调用
public:
SRSender();
virtual ~SRSender();
};
#endif
SRSender.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "SRSender.h"
//控制台颜色控制:滑动窗口中已发送的标记为黄色,未发送的标记为蓝色,已确认的标记为绿色
//构造函数
SRSender::SRSender() :sendbase(0), nextseqnum(0), waitingState(false), windowLength(4), seqLength(3) {
}
//析构函数
SRSender::~SRSender() {
}
//判断窗口是否已满
bool SRSender::getWaitingState() {
if (this->sndpkt.size() >= this->windowLength) {
this->waitingState = true;
}
else this->waitingState = false;
return this->waitingState;
}
//向接收方发送数据报
bool SRSender::send(const Message& message) {
if (this->getWaitingState()) {//窗口满,拒绝发送
return false;
}
this->packetWaitingAck.acknum = -1; //忽略该字段,发送方报文用不到
this->packetWaitingAck.seqnum = this->nextseqnum; //报文段序号nextseqnum
this->packetWaitingAck.checksum = 0; //初始化校验和
memcpy(this->packetWaitingAck.payload, message.data, sizeof(message.data)); //设置报文段内容
this->packetWaitingAck.checksum = pUtils->calculateCheckSum(this->packetWaitingAck);//设置校验和
//创建带状态分组
pktWithState packet0;
packet0.ack = false;
packet0.pkt = this->packetWaitingAck;
this->sndpkt.push_back(packet0);//发送方缓存窗口内的报文段
pUtils->printPacket("发送方发送报文", this->packetWaitingAck);
//此处不需要判断,因为要给所有分组设置定时器
pns->startTimer(SENDER, Configuration::TIME_OUT, this->packetWaitingAck.seqnum);
//注意序号的二进制位数的限制,所以要取余
this->nextseqnum = (this->nextseqnum + 1) % int(pow(2, this->seqLength));
//向网络层交付数据
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
return true;
}
void SRSender::receive(const Packet& ackPkt) {
//只考虑校验和正确的ACK,如果收到不正确的ACK不作任何处理(因为是累加确认)
int checksum = pUtils->calculateCheckSum(ackPkt);
//还需要考虑收到的ACK是否对应窗口中的编号,即ackPkt->acknum是否在[base,base+sndpkt.size()-1]内
int seqLen = int(pow(2, this->seqLength));
int offacknum = (ackPkt.acknum - this->sendbase + seqLen) % seqLen;
//ACK正确且在窗口范围内,并且该分组ACK未确认
if (checksum == ackPkt.checksum && offacknum < this->sndpkt.size() && !this->sndpkt[offacknum].ack) {
//标记相应的分组已经收到,并关闭其定时器
this->sndpkt[offacknum].ack = true;
pns->stopTimer(SENDER, ackPkt.acknum);
pUtils->printPacket("发送方正确收到确认", ackPkt);
//控制台可视化移动前滑动窗口
//将移动前滑动窗口移动信息写入日志文件
ofstream ofs;
ofs.open("log.txt", ios::app);
cout << "\n发送方移动前滑动窗口为:[";
ofs << "\n发送方移动前滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i < this->sndpkt.size()) {
if (this->sndpkt[i].ack) color(175);
else color(224);
}
else color(159);
cout << (this->sendbase + i) % seqLen;
ofs << (this->sendbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]" << endl;
ofs << "]" << endl;
ofs.close();
//判断sendbase对应的分组ack是否为true,我们只需要遍历sndpkt即可
//如果发送方缓存非空且sendbase开始的分组ack为true
while (this->sndpkt.size() > 0 && this->sndpkt.front().ack) {
this->sndpkt.erase(this->sndpkt.begin(), this->sndpkt.begin() + 1);
this->sendbase = (this->sendbase + 1) % seqLen;
}
//控制台可视化移动后滑动窗口
//将移动后滑动窗口移动信息写入日志文件
ofs.open("log.txt", ios::app);
cout << "发送方移动后滑动窗口为:[";
ofs << "发送方移动后滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i < this->sndpkt.size()) {
if (this->sndpkt[i].ack) color(175);
else color(224);
}
else color(159);
cout << (this->sendbase + i) % seqLen;
ofs << (this->sendbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]\n" << endl;
ofs << "]" << endl;
ofs.close();
}
else if (checksum != ackPkt.acknum) {//ACK传输出错,不作任何处理
pUtils->printPacket("ACK数据校验错误", ackPkt);
}
else pUtils->printPacket("已收到过该ACK确认", ackPkt);
}
void SRSender::timeoutHandler(int seqNum) {
//超时只重传序号为seqNum的packet
pns->stopTimer(SENDER, seqNum);
pns->startTimer(SENDER, Configuration::TIME_OUT, seqNum);
int seqLen = int(pow(2, this->seqLength));
int offacknum = (seqNum - this->sendbase + seqLen) % seqLen;//序号对应的packet在sndpkt中的下标
pUtils->printPacket("发送方定时器超时,重发全部缓存报文", this->sndpkt[offacknum].pkt);
pns->sendToNetworkLayer(RECEIVER, this->sndpkt[offacknum].pkt);
}
6.3 SR协议接收方实现
SRReceiver.h
:
#ifndef STOP_WAIT_RDT_RECEIVER_H
#define STOP_WAIT_RDT_RECEIVER_H
#include "RdtReceiver.h"
#include "SRSender.h"
#include "stdafx.h"
#include <vector>
class SRReceiver :public RdtReceiver {
private:
int windowLength; //接收方窗口大小
int seqLength; //序号的二进制位数
int rcvbase; //接收方基序号
vector<pktWithState> rcvpkt; //接收方缓存
Packet lastAckPkt; //收到分组对应的ACK数据报
public:
SRReceiver();
virtual ~SRReceiver();
public:
void receive(const Packet& packet); //接收报文,将被NetworkService调用
};
#endif
SRReceiver.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "SRReceiver.h"
//控制台颜色控制:滑动窗口中已收到的标记为粉色,失序未收到的标记位灰色,未失序未收到的标记为蓝色
//构造函数
SRReceiver::SRReceiver() :windowLength(4), seqLength(3), rcvbase(0) {
//初始化数据包
lastAckPkt.acknum = -1; //初始状态下,上次发送的确认包的确认序号为-1,使得当第一个接受的数据包出错时该确认报文的确认号为-1
lastAckPkt.checksum = 0;
lastAckPkt.seqnum = -1; //忽略该字段
for (int i = 0; i < Configuration::PAYLOAD_SIZE; i++) {
lastAckPkt.payload[i] = '.';
}
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
//为了解决失序的问题,我们提前初始化窗口中的报文,根据下标进行插入即可避免失序问题难以解决
for (int i = 0; i < this->windowLength; ++i) {
pktWithState packet0;
packet0.ack = false; //注意此处接收方的rcvpkt[i].ack是该分组是否提交的标志
packet0.pkt.seqnum = -1;
packet0.pkt.acknum = -1;
packet0.pkt.checksum = -1;
this->rcvpkt.push_back(packet0);
}
}
//析构函数
SRReceiver::~SRReceiver() {
}
//接收到packet报文
void SRReceiver::receive(const Packet& packet) {
//检查校验和是否正确
int checkSum = pUtils->calculateCheckSum(packet);
//检查报文序号是否在窗口内
int seqLen = int(pow(2, this->seqLength));
int offseqnum = (packet.seqnum - this->rcvbase + seqLen) % seqLen;//下标
//正确收到报文,报文序号在发送方窗口内,收到的是不重复的报文
if (checkSum == packet.checksum && offseqnum < this->windowLength && !this->rcvpkt[offseqnum].ack) {
this->rcvpkt[offseqnum].ack = true;
this->rcvpkt[offseqnum].pkt = packet;
//窗口可视化
//移动窗口的过程录入日志
ofstream ofs;
ofs.open("log.txt", ios::app);
cout << "\n接收方移动前滑动窗口为:[";
ofs << "\n接收方移动前滑动窗口为:[";
//首先获得下标最大的ack为true的下标
int maxSeqNum=0;
for (int i = 0; i < this->windowLength; ++i) {
if (this->rcvpkt[i].ack) maxSeqNum = i;
}
for (int i = 0; i < this->windowLength; ++i) {
if (!this->rcvpkt[i].ack && i < maxSeqNum) {//失序且未收到的分组
color(143);
}
else if (this->rcvpkt[i].ack) {//已收到的分组
color(463);
}
else if (i > maxSeqNum) {
color(159);
}
cout << (this->rcvbase + i) % seqLen;
ofs << (this->rcvbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]" << endl;
ofs << "]" << endl;
ofs.close();
//判断是否可以按序上交
pUtils->printPacket("接收方正确收到发送方的报文", packet);
while (this->rcvpkt.front().ack) {
//取出Message,向上递交给应用层
Message msg;
memcpy(msg.data, this->rcvpkt.front().pkt.payload, sizeof(this->rcvpkt.front().pkt.payload));
pns->delivertoAppLayer(RECEIVER, msg);
//rcvbase+=1
this->rcvbase = (this->rcvbase + 1) % seqLen;
//删除缓存中的第一个packet,尾部再插入一个packet
this->rcvpkt.erase(this->rcvpkt.begin(), this->rcvpkt.begin() + 1);
pktWithState packet0;
packet0.ack = false; //注意此处接收方的rcvpkt[i].ack是该分组是否提交的标志
packet0.pkt.seqnum = -1;
packet0.pkt.acknum = -1;
packet0.pkt.checksum = -1;
this->rcvpkt.push_back(packet0);
}
//窗口可视化
//移动窗口的过程录入日志
ofstream ofs;
ofs.open("log.txt", ios::app);
cout << "接收方移动后滑动窗口为:[";
ofs << "接收方移动后滑动窗口为:[";
//首先获得下标最大的ack为true的下标
int maxSeqNum = 0;
for (int i = 0; i < this->windowLength; ++i) {
if (this->rcvpkt[i].ack) maxSeqNum = i;
}
for (int i = 0; i < this->windowLength; ++i) {
if (!this->rcvpkt[i].ack && i < maxSeqNum) {//失序且未收到的分组
color(143);
}
else if (this->rcvpkt[i].ack) {//已收到的分组
color(463);
}
else if (i > maxSeqNum) {
color(159);
}
cout << (this->rcvbase + i) % seqLen;
ofs << (this->rcvbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]\n" << endl;
ofs << "]" << endl;
ofs.close();
//发送ACK(n)
lastAckPkt.acknum = packet.seqnum; //确认序号等于收到的报文序号
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
pUtils->printPacket("接收方发送确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送确认报文到对方
}
else {
if (checkSum != packet.checksum) {
pUtils->printPacket("接收方没有正确收到发送方的报文,数据校验错误", packet);
}
else {//收到窗口外或者重复的分组,反馈其相应的ACK(n)
pUtils->printPacket("接收方正确收到发送方的报文,但是收到了重复分组", packet);
lastAckPkt.acknum = packet.seqnum; //确认序号等于收到的报文序号
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
pUtils->printPacket("接收方重新发送已经收到过的分组的确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送上次的确认报文
}
}
}
6.4 SR主模块实现
SR.cpp
:
//GBN.cpp:控制台程序入口点
#include "stdafx.h"
#include "Global.h"
#include "RdtSender.h"
#include "RdtReceiver.h"
#include "SRSender.h"
#include "SRReceiver.h"
int main(int argc, char* argv[]) {
system("color F0");
//日志文件分隔头
ofstream ofs;
ofs.open("log.txt", ios::app);
ofs << "********************* 进行一次文件传输 *********************" << endl;
ofs.close();
RdtSender* ps = new SRSender();
RdtReceiver* pr = new SRReceiver();
// pns->setRunMode(0); //VERBOS模式
pns->setRunMode(1); //安静模式
pns->init();
pns->setRtdSender(ps);
pns->setRtdReceiver(pr);
pns->setInputFile("E:\\SR\\input.txt");
pns->setOutputFile("E:\\SR\\output.txt");
pns->start();
delete ps;
delete pr;
delete pUtils; //指向唯一的工具类实例,只在main函数结束前delete
delete pns; //指向唯一的模拟网络环境类实例,只在main函数结束前delete
return 0;
}
部分输出结果如下:
七、TCP协议实现
本次实验的实现要求如下:
● 报文段格式、接收方缓冲区大小和 GBN 协议一样保持不变;
● 报文段序号按照报文段为单位进行编号;
● 单一的超时计时器,不需要估算 RTT 动态调整定时器 Timeout 参数;
● 支持快速重传和超时重传,重传时只重传最早发送且没被确认的报文段;
● 确认号为收到的最后一个报文段序号;
● 不考虑流量控制、拥塞控制
7.1 TCP原理
TCP在IP的不可靠服务基础上提供可靠数据传输服务:
① 流水线方式发送报文段
② 累积确认:只确认最后一个正确按序到达的报文段
③ TCP使用一个重传定时器(最早未确认的报文段)
从上面几点看,TCP采用的机制很像GBN
处理数据流程:
① 从应用程序接收数据
② 将数据封装入报文段中,每个报文段都包含一个序号,序号是该报文段第一个数据字节的字节流编号
③ 启动定时器,超时间隔: TimeOutInterval
④ 超时: 重传认为超时的报文段(最早未确认的报文段),重启定时器
⑤ 收到Ack: 如果是对以前的未确认报文段的确认(Ack>Sendbase),更新SendBase,如果当前有未被确认的报文段,TCP要重启定时器
快速重传:超时周期往往太长,增加重发丢失分组的延时,通过重复的ACK检测丢失报文段,在超时到来之前重传报文段
① 发送方常要连续发送大量报文段,如果一个报文段丢失,会引起很多连续的重复ACK
② 如果发送方收到一个数据的3个重复ACK,它会认为确认数据之后的报文段丢失
我们本个实验实现的TCP与实际的TCP还是有区别的,该TCP几乎与GBN协议相同,但是
7.2 TCP发送方实现
stdafx.h
:
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include <fstream>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <math.h>
// TODO: 在此处引用程序需要的其他头文件
#pragma comment (lib,"E:\\TCP\\netsimlib.lib")
#include <iostream>
using namespace std;
//#pragma warning(disable:4482)
//控制台颜色控制:滑动窗口中已发送的标记为黄色,未发送的标记为蓝色,已确认的标记为绿色
inline void color(int x) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);//设置不同数字的颜色
}
TCPSender.h
:
#ifndef STOP_WAIT_RDT_SENDER_H
#define STOP_WAIT_RDT_SENDER_H
#include "RdtSender.h"
#include "stdafx.h"
#include <vector>
class TCPSender :public RdtSender {
private:
int windowLength; // 发送方窗口大小
int seqLength; // 序号对应的二进制数位数
int sendbase; // 发送方基序号
int nextseqnum; // 下一个将要发送的分组的序号
int redAckNum; //冗余ACK个数
vector<Packet> sndpkt; // 缓存带状态分组
bool waitingState; // 是否处于等待Ack的状态
Packet packetWaitingAck; // 已发送并等待Ack的数据包
public:
bool getWaitingState();
bool send(const Message& message); //发送应用层下来的Message,由NetworkServiceSimulator调用,如果发送方成功地将Message发送到网络层,返回true;如果因为发送方处于等待正确确认状态而拒绝发送Message,则返回false
void receive(const Packet& ackPkt); //接受确认Ack,将被NetworkServiceSimulator调用
void timeoutHandler(int seqNum); //Timeout handler,将被NetworkServiceSimulator调用
public:
TCPSender();
virtual ~TCPSender();
};
#endif
TCPSender.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "TCPSender.h"
//控制台颜色控制:滑动窗口中已发送的标记为黄色,未发送的标记为蓝色
//构造函数
TCPSender::TCPSender() :windowLength(4), seqLength(3), sendbase(0), nextseqnum(0), redAckNum(0), waitingState(false) {
}
//析构函数
TCPSender::~TCPSender() {
}
//判断窗口是否已满
bool TCPSender::getWaitingState() {
if (this->sndpkt.size() >= this->windowLength) {
this->waitingState = true;
}
else this->waitingState = false;
return this->waitingState;
}
//向接收方发送一个packet->与GBN完全相同,发送nextseqnum对应的packet即可,只为base设置定时器
bool TCPSender::send(const Message& message) {
if (this->getWaitingState()) {//窗口满,拒绝发送
return false;
}
this->packetWaitingAck.acknum = -1; //忽略该字段,发送方报文用不到
this->packetWaitingAck.seqnum = this->nextseqnum; //报文段序号nextseqnum
this->packetWaitingAck.checksum = 0; //初始化校验和
memcpy(this->packetWaitingAck.payload, message.data, sizeof(message.data)); //设置报文段内容
this->packetWaitingAck.checksum = pUtils->calculateCheckSum(this->packetWaitingAck);//设置校验和
this->sndpkt.push_back(this->packetWaitingAck);//发送方缓存窗口内的报文段
pUtils->printPacket("发送方发送报文", this->packetWaitingAck);
//只为base设置定时器,定时器超时之后发送sndpkt[base]~sndpkt[nextseqnum-1]
if (this->sendbase == this->nextseqnum) {
pns->startTimer(SENDER, Configuration::TIME_OUT, this->packetWaitingAck.seqnum);
}
//注意序号的二进制位数的限制,所以要取余
this->nextseqnum = (this->nextseqnum + 1) % int(pow(2, this->seqLength));
//向网络层交付数据
pns->sendToNetworkLayer(RECEIVER, this->packetWaitingAck);
return true;
}
//接收ACK的同时要支持快速重传
void TCPSender::receive(const Packet& ackPkt) {
//只考虑校验和正确的ACK,如果收到不正确的ACK不作任何处理(因为是累加确认)
int checksum = pUtils->calculateCheckSum(ackPkt);
//还需要考虑收到的ACK是否对应窗口中的编号,即ackPkt->acknum是否在[base,base+sndpkt.size()-1]内
int seqLen = int(pow(2, this->seqLength));
int offacknum = (ackPkt.acknum - this->sendbase + seqLen) % seqLen;
if (checksum == ackPkt.checksum && offacknum < this->sndpkt.size()) {
//控制台可视化移动前滑动窗口
//将移动前滑动窗口移动信息写入日志文件
ofstream ofs;
ofs.open("log.txt", ios::app);
cout << "\n移动前滑动窗口为:[";
ofs << "\n移动前滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i < this->sndpkt.size()) color(224);
else color(159);
cout << (this->sendbase + i) % seqLen;
ofs << (this->sendbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]" << endl;
ofs << "]" << endl;
ofs.close();
//移动滑动窗口base=acknum+1,同时删除缓存中已经确认的packet
pUtils->printPacket("发送方正确收到确认", ackPkt);
//因为重启定时器前也需要关闭定时器,所以没必要判断,无脑关就行了,但是要注意关闭的定时器的序号!
pns->stopTimer(SENDER, this->sendbase);
while (this->sendbase != (ackPkt.acknum + 1) % seqLen) {
this->sndpkt.erase(this->sndpkt.begin(), this->sndpkt.begin() + 1);
this->sendbase = (this->sendbase + 1) % seqLen;
}
//控制台可视化移动后滑动窗口
//将移动后滑动窗口移动信息写入日志文件
ofs.open("log.txt", ios::app);
cout << "移动后滑动窗口为:[";
ofs << "移动后滑动窗口为:[";
for (int i = 0; i < this->windowLength; ++i) {
if (i < this->sndpkt.size()) color(224);
else color(159);
cout << (this->sendbase + i) % seqLen;
ofs << (this->sendbase + i) % seqLen;
if (i != this->windowLength - 1) {
color(240);
cout << ",";
ofs << ",";
}
}
color(240);
cout << "]\n" << endl;
ofs << "]" << endl;
ofs.close();
//每次收到正确ACK都要将冗余置零
this->redAckNum = 0;
//判断定时器处理
if (this->sndpkt.size() != 0) {//非空,仍需重启定时器
pns->startTimer(SENDER, Configuration::TIME_OUT, this->sendbase);//以基准包的序号开启计时器
}
}
else if (checksum != ackPkt.checksum) {//ACK传输出错,不作任何处理
pUtils->printPacket("ACK数据校验错误", ackPkt);
}
//接收方未按序收到正确的报文时,会反馈上次收到的ACK,即这个冗余的ACK序号只可能是base-1
else {
pUtils->printPacket("发送方已正确收到过该报文确认", ackPkt);
this->redAckNum++;
if (this->redAckNum == 3 && this->sndpkt.size() > 0) {
//快速重传写入日志
ofstream ofs;
ofs.open("log.txt", ios::app);
ofs << "进行一次快速重传,重传分组的序号为:" << this->sendbase << endl;
ofs.close();
color(252);
pUtils->printPacket("发送方进行快速重传,重传最早发送且没被确认的报文段", this->sndpkt.front());
color(240);
pns->sendToNetworkLayer(RECEIVER, this->sndpkt.front());
this->redAckNum = 0;
}
}
}
void TCPSender::timeoutHandler(int seqNum) {
pns->stopTimer(SENDER, seqNum); //首先关闭定时器
pns->startTimer(SENDER, Configuration::TIME_OUT, seqNum); //重新启动发送方定时器
//重新发送最早发送且没被确认的报文段
pUtils->printPacket("发送方定时器时间到,重发最早发送且没被确认的报文段", this->sndpkt.front());
pns->sendToNetworkLayer(RECEIVER, this->sndpkt.front());
}
7.3 TCP接收方实现
TCPReceiver.h
:
#ifndef STOP_WAIT_RDT_RECEIVER_H
#define STOP_WAIT_RDT_RECEIVER_H
#include "RdtReceiver.h"
#include "stdafx.h"
class TCPReceiver :public RdtReceiver {
private:
int expectedseqnum; //期望收到的packet序号
int seqLength; //序号二进制数位数
Packet lastAckPkt; //上次发送的ACK报文
public:
TCPReceiver();
virtual ~TCPReceiver();
public:
void receive(const Packet& packet); //接收报文,将被NetworkService调用
};
#endif
TCPReceiver.cpp
:
#include "stdafx.h"
#include "Global.h"
#include "GBNReceiver.h"
//构造函数
GBNReceiver::GBNReceiver() :expectedseqnum(0), seqLength(3) {
//因为我们设计的GBN中序号从0开始,因此初始化的acknum=-1
lastAckPkt.acknum = -1; //初始状态下,上次发送的确认包的确认序号为-1,使得当第一个接受的数据包出错时该确认报文的确认号为-1
lastAckPkt.checksum = 0;
lastAckPkt.seqnum = -1; //忽略该字段
for (int i = 0; i < Configuration::PAYLOAD_SIZE; i++) {
lastAckPkt.payload[i] = '.';
}
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
}
//析构函数
GBNReceiver::~GBNReceiver() {
}
//接收到packet报文,反馈ACK
void GBNReceiver::receive(const Packet& packet) {
//检查校验和是否正确
int checkSum = pUtils->calculateCheckSum(packet);
//如果校验和正确,同时收到报文的序号等于接收方期待收到的报文序号一致
if (checkSum == packet.checksum && this->expectedseqnum == packet.seqnum) {
pUtils->printPacket("接收方正确收到发送方的报文", packet);
//取出Message,向上递交给应用层
Message msg;
memcpy(msg.data, packet.payload, sizeof(packet.payload));
pns->delivertoAppLayer(RECEIVER, msg);
lastAckPkt.acknum = packet.seqnum; //确认序号等于收到的报文序号
lastAckPkt.checksum = pUtils->calculateCheckSum(lastAckPkt);
pUtils->printPacket("接收方发送确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送确认报文到对方
this->expectedseqnum = (this->expectedseqnum + 1) % int(pow(2, this->seqLength));//接收序号在0-1之间切换
}
else {
if (checkSum != packet.checksum) {
pUtils->printPacket("接收方没有正确收到发送方的报文,数据校验错误", packet);
}
else {
pUtils->printPacket("接收方没有正确收到发送方的报文,报文序号不对", packet);
}
pUtils->printPacket("接收方重新发送上次的确认报文", lastAckPkt);
pns->sendToNetworkLayer(SENDER, lastAckPkt); //调用模拟网络环境的sendToNetworkLayer,通过网络层发送上次的确认报文
}
}
7.4 TCP主模块
TCP.cpp
:
//GBN.cpp:控制台程序入口点
#include "stdafx.h"
#include "Global.h"
#include "RdtSender.h"
#include "RdtReceiver.h"
#include "TCPSender.h"
#include "TCPReceiver.h"
int main(int argc, char* argv[]) {
system("color F0");
//日志文件分隔头
ofstream ofs;
ofs.open("log.txt", ios::app);
ofs << "********************* 进行一次文件传输 *********************" << endl;
ofs.close();
RdtSender* ps = new TCPSender();
RdtReceiver* pr = new TCPReceiver();
// pns->setRunMode(0); //VERBOS模式
pns->setRunMode(1); //安静模式
pns->init();
pns->setRtdSender(ps);
pns->setRtdReceiver(pr);
pns->setInputFile("E:\\TCP\\input.txt");
pns->setOutputFile("E:\\TCP\\output.txt");
pns->start();
delete ps;
delete pr;
delete pUtils; //指向唯一的工具类实例,只在main函数结束前delete
delete pns; //指向唯一的模拟网络环境类实例,只在main函数结束前delete
return 0;
}