目录
一、实验目的与要求
实验目的:
1. 理解网络传输过程中可能出现的差错;
2. 理解网络传输中的稳定传输机制和差错处理方法;
3. 理解面向无连接的稳定传输机制;
4. 了解TCP稳定传输机制;
5. 了解滑动窗口机制;
6. 理解面向无连接的稳定传输机制;
7. 理解和掌握不同网络情况下,不同稳定传输机制的差异;
8. 通过对传输机制进行比较。
实验要求:
1. 理解和掌握可靠传输机制的原理;
2. 基于提供的资料和代码,完成任务要求;
3. 按照任务要求进行实验,并按要求绘制实验结果图;
4. 对实现代码和实验结果进行截图展示;
5. 撰写实验报告。
二、实验过程
任务1:rdt2.1理解
1. 解释和说明rdt2.1的状态转移机制:
发送方(服务端):总共有四种状态,分别是:
①等待上层偶数包:发送方首先会创建一个数据包(编号seq为偶数),并将它发送到接收方;然后,发送方就会转变为状态②。
②等待ACK/NAK,偶数状态:发送方发送完偶数编号的数据包后,等待来自接收方的确认 (ACK) 或者非确认 (NAK)信息 。如果接收到的是 ACK,表明数据包已经被接收方正确接收,释放数据包空间,发送方转为状态③;而如果收到的是NAK或者被破坏的ACK/NAK,则重新发送上一次发送的数据包,并保持状态不变。
③等待上层奇数包:与①相似,但编号为奇数。执行完后转为状态④。
④等待ACK/NAK,奇数状态:与②相似,但是此处如果收到了正确的ACK,需要判断是否已经发完所有数据包,如果没有,再转为状态①。
接收方(客户端):总共有两种状态:
①等待来自下层的偶数数据包:此状态对应的是发送方的状态①(和出现重发情况的状态②),首先等待发送方的数据包,收到后需要判断数据包是否损坏、是否为偶数编号。如果无损坏且为偶数编号,则返回ACK,转为状态②;如果有损坏,就返回NAK,保持状态不变;如果无损坏但是收到的是奇数编号的包,说明丢包,重新发送上一次发送的ACK,保持状态不变。
②等待来自下层的奇数数据包:与①相似,此处不再赘述。
2. 利用clumsy模拟器模拟网络状态,在不同的网络状态下进行实验,并记录相关数据:
下载并安装clumsy模拟器,按照要求设置,并开始运行,即可模拟网络环境波动导致的数据丢包与损坏:
图:clumsy模拟器设置
设置不同的数据包错误频率,在每种频率下进行一百次实验(此处我对主函数中对发送/接收函数的调用部分做了修改,设置循环,以方便统计,加快实验速度),统计数据包总数、数据包错误频率、有效吞吐量三个变量,并作图,结果如图:
表:各个数据包错误频率下的实验结果
图:数据包总数-数据包错误频率图
图:有效吞吐量-数据包错误频率图
3. 根据实验结果思考并回答问题
①当数据包发生错误时(在一定的数据包错误频率情况下),重发单个数据包是否能解决问题?从数据包发生错误到恢复正常发送状态的过程是怎样的?
答:重发单个数据包可以解决此数据包的错误问题,但是如果数据包错误频率较高,过多的重发会导致传输效率降低。数据包发生错误→接收方判断数据包出错,向发送方返回NAK包→发送方收到NAK包,重新发送此数据包→接收方收到正确数据包,返回ACK包→发送方收到ACK包,发送恢复正常。其中,如果重新发送的数据包再次出错,就需要反复进行:(接收方判断数据包出错,向发送方返回NAK包→发送方收到NAK包,重新发送此数据包)的过程。
②根据实验结果图,分析数据包总数、有效吞吐量与数据包错误频率之间的数学关系(线性关系?指数关系?)
答:由图可见,数据包总数随着数据包错误频率增大而逐渐增大,且增大速度越来越快,而有效吞吐量随着错误频率增大而逐渐减小,且减小速度越来越快。二者应当均为指数关系。说明数据包错误频率的增加会导致传输效率的快速降低,对传输有较大影响。
任务2:rdt2.2实现
1. 解释、说明rdt2.2的状态转移机制(主要分析与rdt2.1的不同之处):
在rdt2.1中,使用NAK包表示数据出现错误,提示发送端重新发送数据包,而在rdt2.2中舍弃了NAK包的使用,只返回ACK,发送方需要根据收到ACK的编号正确与否判断接收方是否收到的正确的数据包。
在发送方(服务端)中:
原先的状态②有所改变:由原先的等待ACK或NAK变为了等待ACK偶序包,原先判断是否收到ACK的语句变成了是否收到偶序ACK,判断是否收到NAK的语句变成了是否收到奇序的ACK。
原先的状态④(等待ACK/NAK,奇数状态)与此相似,变成了等待ACK奇序包,判断语句的改变也与②的改变对应。
在接收方中,由于舍弃了NAK的使用,状态更为简单:
状态①:等待来自下层的偶数数据包:如果收到了损坏的数据包或者编号为奇数的数据包,就发送编号为奇数的ACK包,而如果收到了未损坏的偶数数据包,就发送编号为偶数的ACK包,并转为状态②
状态②:等待来自下层的奇数数据包:整体部分与①相似,奇数偶数互为对应。
2.实现rdt2.2的状态转移机制:
①在发送端:
修改后的状态名称如下图:
图:发送方状态名称
使用判断编号奇偶性的方式判断ACK包的奇偶性:
图:判断ACK奇偶性的函数
接着根据状态机以及我的理解改变判断语句即可。
②在接收端:将发送NAK包的语句删除,并依据状态机更改状态转移判断条件:
图:接收端主要更改的部分
3. 分析rdt2.1和rdt2.2的机制差异:
对于以下情况:连续三个包满足以下条件:接收方正常接收数据包、接收方返回的ACK发生错误、发送方重发数据包再次发生错误。rdt2.1和rdt2.2的处理方法分别是什么?哪一种机制在出现这种状况时表现较好?为什么?
答:
①rdt2.1的处理方式:发送方收到错误的ACK,重新发送数据包→接收方收到错误的重发数据包,发送NAK包→发送方收到NAK包,再次重新发送数据包→接收方收到正确数据包,发送ACK包……
②rdt2.2的处理方式:发送方收到错误的ACK,重新发送数据包→接收方收到错误的重发数据包,重新发送上一次发送的ACK包→发送方收到正确的ACK包,开始进行下一个数据包的发送……
③rdt2.2表现较好:因为rdt2.2使用了上一次发送的ACK包代替了NAK包,在接收方收到了错误的重发数据包后,rdt2.1发送的是NAK,将会导致发送方第三次发送此数据包。而rdt2.2发送的是ACK,可以使发送方直接开始下一个数据包的发送,减少了一次往返,加快了运行效率。
4.进行实验,统计实验结果并作图:
在每个数据包错误频率下进行一百次实验,实验结果统计以及绘图如图所示:
图:实验结果
图:数据包总数-数据包错误频率图
图:有效吞吐量-数据包错误频率图
由图可见,rdt2.2中各个数据包错误频率下的数据包总数均有所减少,有效吞吐量均有所增加,说明rdt2.2的传输效率相较于rdt2.1有了一定量的提升。
原因可能是:
①在上文第3点所分析的情况中,rdt2.2可以避免一次往返,使运行时间减少,而这种情况在数据包错误频率较高的环境下更容易出现,从而导致rdt2.2在数据包错误频率更高的环境下相较于rdt2.1有更显著的优势。
②舍弃了NAK,代码有所精简。
任务3:rdt3.0实现
1. 解释、说明rdt3.0的状态转移机制:
rdt3.0在rdt2.2的基础上加入了超时重传机制,当发送方发送一个数据包后,会等待一定的时间来接收ACK。如果在这段时间内发送方没有收到ACK,那么它会假定数据包或ACK在传输中丢失,并会重新发送数据包。
2. 回答以下问题:
①在rdt3.0的发送方中,触发timeout事件时,定时器的内容是否有区别?如果有,有什么样的区别?
答:无区别,都是计时正好到达一秒的状态。
②每次触发timeout事件时,是否会改变发送方的状态?
答:不会,保持当前状态,继续等待接收方的确认。
3. 实现rdt3.0的状态转移机制:
将CALLBACK timeout_function()函数在发送端中实现:
也即:一旦超时,就重新发送数据包,进行相关统计,并重启计时器。
4. 回答以下问题:
在数据包错误的情况下,rdt3.0和rdt2.2中的现象是什么?有什么区别?
rdt2.2:发送方收到错误的ACK后,直接重新发送上一次发送的数据包,回到等待接收方确认的状态;
rdt3.0:发送方收到错误的ACK后,回到等待接收方确认的状态,直到计时器超时再重发数据包。
区别:当遇到数据包错误,rdt3.0引入了计时器机制,将错误的返回与丢包同等对待,而rdt2.2则直接重发数据包。
在数据包丢失的情况下,rdt3.0是如何处理的?
答:数据包丢失后,由于接收方不会有返回,一段时间后发送方的计时器就会超时,接着发送方将会重发数据包,继续等待接收方的确认。
5. 使用clumsy模拟数据包丢失状况,进行实验,统计并绘图:
将数据包损坏率固定为1%,丢包率分别设置为:0.3%、0.5%、1%、2%、3%、5%、10%、20%和30%的实验结果如图所示:
表:实验结果
图:数据包总数-丢包率关系图
图:有效吞吐量-丢包率关系图
由图可见,数据包总数与丢包率的关系和数据包总数与数据包损坏率的关系接近,但是有效吞吐量会随着丢包率的上升而快速减小,减小速度远快于数据包损坏率的影响(应当为系数大得多的指数关系),可见丢包对网络的影响比数据损坏更为严重。这是因为一旦出现丢包,只能通过等待进行判断,等待过程中无法进行其他的传输,从而导致了传输过程被拖慢。
除此之外,我还对所示状态图所示的状态机进行了优化:在状态机所示的状态中,如果收到了错误的ACK,就继续等待,直到计时器超时再重发数据包。而我将其改成了:收到错误的ACK就直接重发数据包,不等待超时。这样一来,遇到错误ACK的情况就可以避免等待,提高传输效率。
优化后的rdt3.0实验测试如图所示:
表:优化后的rdt3.0实验结果
可见主要是较低丢包率下的有效吞吐量出现了较大的增加。将其结果与优化前的rdt3.0进行绘图比较,结果如下:
图:优化前后的有效吞吐量对比
任务4:回退N步(GBN)机制实现
1. 理解GBN状态转移机制:
发送方机制:
初始化阶段将发送窗口的起始位置(在此之前所有的包都是已发送且确认)base与下一个要发送的包的编号nextseqnum一起置为1。
接着开始准备发送:首先将数据包打包,循环判断nextseqnum是否小于base+N,其中N为发送窗口的长度:
如果是,说明发送窗口中还有数据包没有发送,进入循环,发送编号为nextseqnum的包。接着判断nextseqnum是否等于base:如果是,说明刚刚发送的数据包是当前发送窗口的第一个包,启动计时器;如果不是,则不进行操作。最后将nextseqnum自增1。
而如果不是,说明当前发送窗口中的数据包已经全部发送,退出循环,等待接收方的返回。
发送完成后开始等待接收,如果一段时间后没收到返回的ACK包,计时器超时,说明数据包传输或者ACK包返回时出现了丢包,重新发送上一次发送的一系列包。
如果收到了正确的ACK包,就将base置为此ACK包的编号加1(等同于将发送窗口的位置往前推动1),如果此时base与nextseqnum相等,说明已经收到了所有ACK,停止计时器,否则重启计时器。
如果收到了错误的ACK包,就不进行操作,继续等待,直到收到新的正确的包或者超时。
接收方机制:
初始化阶段将预计收到的数据包编号expectedseqnum置为1,并创建一个编号为0的ACK包。
接着开始准备接收,如果收到了未被损坏且编号正确的包,就返回相应编号的ACK,并将expectedseqnum自增1。如果收到了损坏的包或者编号错误的包,就返回上一次发送的ACK。
2. 回答以下问题
①在GBN机制中,如何利用ACK序号对数据包进行判断?
答:当收到一个编号为n的ACK,即可认为n之前的数据包全部被接收方正确收到。
②解释和说明GBN机制如何对乱序数据包的情况进行处理的?
答:如果接收方收到的不是顺序数据包,就将新收到的乱序数据包直接丢弃,返回上一次发送的ACK。
③在给出GBN机制为何能够完成rdt3.0机制中的所有条件,最后如何将所有状态合并为一个状态的?
答:因为GBN机制使用了滑动窗口,不需要再对发送的每个数据包都进行判断(也就是说不需要对每个数据包都等到其对应的ACK),当收到某个ACK即可说明在此之前的所有数据包均正确发送,接着可以由此刷新窗口的位置,继续发送新的数据包。
3. 实现GBN机制
发送端:
在send_packets()函数中,我首先对装填数据的部分进行了补充:原先只是将数据包的data部分赋值,我补充了为数据包的编号seq、数据包的种类type的赋值,还添加了校验码的计算。其次,我设计了发送数据包的循环,具体如下:
图:装填数据包
接下来,我设计了发送数据包的部分:首先,如果数据包并没有发送完成,就进入循环,接下来判断是否已经发送完当前发送窗口中的所有数据包,如果没有,就可以继续发送,反之进入下一次循环判断。
图:发送数据包
在receive_naks()函数中,我设计了接收返回的ACK的函数:首先判断数据包是否已经被全部发送确认,如果还没,就进入循环,准备接收ACK,接下来判断ACK是否损坏,如果损坏,直接进入下一次循环,等待接收下一个ACK,如果未损坏,就将发送窗口向前滑动到收到的ACK的编号+1的位置,并重置计时器。
图:receive_naks()函数设计
接收端:
由于状态图中并没有使用NAK,接收端当收到发送端发来的错误的数据包时,就直接返回上一次发送的ACK,与rdt2.2的逻辑类似。我主要补充了接收数据包并返回ACK的部分函数:
图:接收方核心函数
当收到数据包时,要进行三种判断:①如果数据包损坏:直接返回上一次发送的数据包;②如果数据包未损坏且序号正确:将已收到的最大数据包编号自增1,返回对应编号的ACK;③如果收到了未损坏但乱序的数据包:与①同样处理。
需要特别注意的是,每次发送sndpkt之前,都需要将其重新赋值,因为make_pkt返回的临时地址在if函数执行完后就会释放,这将会使sndpkt回到最初的值。
4. 利用clumsy模拟器在不同网络状态下进行实验,并通过作图进行分析:
发送窗口的大小设置为10,Clumsy模拟器的一系列参数设置同任务3,实验结果如图所示:
表:实验数据
图:数据包总数-丢包率关系图
图:有效吞吐量-丢包率关系图
由图可见,相较于rdt3.0,GBN机制下的数据包总数和有效吞吐量均大大增加,这是因为GBN机制中一次性会发送多个数据包,这样本身可以大幅提升数据传输的效率,但是一旦遇到数据丢包或者错误,就需要传送一系列数据包,这就导致了数据包总量的大大增加。
任务5:面向无连接的可靠传输机制(GBN):
1. 解释、说明基于NAK的稳定传输机制:
主体部分与任务4相近,此处主要分析不同之处:
发送端:
一个线程发送数据包,另一个线程准备接收来自接收端的NAK,如果收到了NAK,说明发送的数据包出现了错误,发送线程回退至NAK的编号处。
接收端:
保持接收来自发送端的数据包,如果收到的数据包出错(或者顺序不对),发送期望接收到的数据包编号对应的NAK。
增加计时器:当收到数据包时刷新计时器,如果计时器超时,说明数据包出现丢包,返回NAK并重启计时器,如果最后一个数据包被正确接收,就停止计时器。
2. 回答下列问题:
①理解停等协议和非停等协议的区别:在停等协议中,发送方只有确定接收方已经正确接收当前发生的数据包时,才会继续发送新的数据,而在非停等协议中,发送方可以持续发送数据,直到触发错误处理事件(数据包出错、丢包等);那么,面向无连接基于NAK机制的稳定传输机制属于哪种协议?它应该如何触发错误处理事件?
答:(1)非停等协议;
(2)对于接收方:如果收到的数据包出错或乱序(也即丢包),接收方将会返回期望接收到的数据包编号对应的NAK;设置计时器,当收到数据包时刷新计时器,如果计时器超时,往往是发送方已经发送完所有的数据包,但是最后面的一个或多个数据包出现了丢包问题,也返回NAK。
对于发送方,当收到NAK,就重新从NAK对应编号的数据包开始进行发送。
②在面向无连接的数据传输过程中,接收方并不需要使用ACK向发送方确认数据传输成功,而是使用NAK告知发送方数据发送失败,那么在这种情况下,数据发送的过程是怎样的?请画出无连接数据传输过程中的时序图,并分析NAK机制和ACK机制的区别;
答:(1)我绘制了一张传输过程中出现了一个错误数据包的示意图:
图:无连接数据传输过程中的时序图
(2)对于接收方:NAK机制下接收方无需对每一个数据包进行一次返回,仅在数据包异常时对发送方进行必要的提醒,而ACK机制需要对每个数据包进行返回。
对于发送方:NAK机制下发送方可以一直发送数据包,直到收到NAK才进行回退处理;而ACK机制下发送方需要收到对应的ACK才可继续发送。
③在无连接数据传输过程中,一旦发生数据内容错误的情况,在不使用ACK的情况下,可否用NAK机制处理?如何处理?
答:可以;当接收方收到错误的数据包后,返回NAK,而发送方收到NAK后,即可判断数据内容出现了错误,重新回到发生错误的数据包出开始发送即可。
④接收端如何发现丢包?
答:当数据包乱序,即可判断出现了丢包;为了判断最后一个或几个数据包出现丢包的情况,接收方设置了计时器:每次收到数据包重置计时器,如果接收未结束且一段时间未收到数据包,计时器超时,判断为上述情况,返回NAK,重置计时器继续等待。
⑤在无连接数据传输过程中,当NAK数据包本身发生错误或丢失时,NAK机制是如何处理的?
答:接收方返回的NAK出错,发送方将会无视这个错误的NAK,继续发送,但是在此之后发送的数据包对于接收方都是乱序数据包,接收方依然会返回对应的NAK。最后一个数据包出现错误或丢包,此时返回的NAK还错误或丢失的情况:接收方由于收不到数据包,计时器将会超时,再次发送相同NAK,重置计时器继续等待。
⑥当发送方收到NAK数据包时,应如何操作?
答:重新回到NAK对应编号的数据包开始发送。
⑦当接收方已正确接收丢失数据包的重传时,应如何操作?
答:处理数据,将收到的数据包的最大编号max_seq_received更新,并准备接受下一个数据包。
⑧当发送方将丢失数据重传完毕后,下一步应该怎么做?
答:继续发送下一个数据包即可。
3. 了解并行事件处理模式
请根据对面向无连接基于NAK机制的稳定传输理解,回答以下问题:
①在面向无连接基于NAK的稳定传输机制中,哪些事件需要进行并行处理?
答:发送端:发送数据包的同时需要一直等待来自接收方的返回。接收方:接收数据包的同时需要一直维持计时器,当出现超时进行相应处理。
②为什么面向无连接基于NAK的稳定传输机制无法使用停等的方式进行传输?
答:因为在此机制下,发送方发送的正确数据包将不存在返回,如果使用停等模式,发送方在发送完某个正确数据包后就会一直等待来自接收方的返回,而由于不存在返回,发送就无法继续。
4. 实现基于NAK的稳定传输机制:
发送端:
发送数据包的部分可以简化为一个简单的循环:如果数据包没发送完就继续发送:
图:发送数据包
对NAK的处理:如果收到NAK就将下一个发送的数据包编号nextseqnum回退至NAK对应的编号:
图:处理NAK
接收端:
由于使用NAK进行返回,需要增加以下改动:
①增加计时器,对丢包情况返回NAK:
图:超时处理函数
②改变接收数据包后的处理:如果收到的是错误的或者乱序的数据包,就返回NAK,而如果收到了正确的顺序数据包,统计数据包编号并重启计时器即可,无需进行其他操作。
图:接收数据包逻辑
5. 利用clumsy模拟器在不同网络状态下进行实验,并通过作图进行分析:
上述的流程中,发送方并不能知道何时发送已经结束,因为最后一个或多个数据包出现丢包的情况只能在收到接收方返回的NAK时才能知道,但返回的NAK本身也可能出现丢包,从而一直没有被发送方收到,因此发送方需要一直等待。而为了方便实验数据统计,我给接收方新设置了一个功能:当确认收到最后一个数据包,返回多个ACK,以确保发送方能收到至少一个,当发送方收到ACK,就统计并输出实验结果。代码如下:
图:返回ACK机制
图:发送方的处理
Clumsy模拟器的一系列参数设置同任务3,实验结果如图所示:
图:实验数据
图:数据包总数-丢包率关系图
图:有效吞吐量-丢包率关系图
由图可知,相较于任务4中基于ACK 的GBN机制,基于NAK的GBN机制的数据包总量大大减少,有效吞吐量大大增加。
有效吞吐量-丢包率关系图的分析:当丢包率较小时,发送往往在瞬间完成,故有效吞吐量相当大,而当丢包率较高,很可能出现最后一个或多个数据包出现丢包的情况,此时需要等待计时器超时,从而使传输时间增加,导致有效吞吐量快速下降。
任务6:量化分析:
1. 对比基于ACK的数据传输机制和基于NAK的无连接数据传输机制两种机制在数据包错误情况下的差异:实验结果如图:
图:不同数据包错误频率时的实验结果
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
2. 对比两种机制在数据包丢包情况下的差异:
实验测试结果如图:
图:实验数据
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
3. 对比两种机制在数据包乱序情况下的差异:
实验测试结果如图:
图:实验数据
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
4. 实验分析:
①分析上述实验,比较两种机制的性能:
答:在上述实验中,基于NAK的无连接数据传输机制(以下简称NAK机制)具有远优于基于ACK的数据传输机制(以下简称ACK机制)的性能表现。主要体现在:(1)相同条件下NAK机制发送的数据包总数往往都只有ACK机制的一半左右;(2)相同条件下NAK机制的有效吞吐量远远大于ACK机制,在同一张图上甚至可观察到ACK机制有效吞吐量均接近0的现象,实际实验中也如此,NAK机制所需的单次实验时间远小于ACK机制。
②根据以上三种情况的实验结果,分别分析不同机制中,数据包总数、有效吞吐量与数据包错误频率、数据包丢包率、数据包乱序比例之间的数学关系:
答:对于NAK机制:数据包总数与数据包错误频率、数据包丢包率、数据包乱序比例之间的关系均接近线性关系;有效吞吐量与丢包率接近线性关系,与错误率、乱序率为指数关系。
对于ACK机制:数据包总数与错误率、乱序率的关系接近线性,但是与丢包率的关系呈现指数关系的趋势;有效吞吐量与三者均呈指数关系。
③基于本次实验的所有内容,综合比较两种机制的各自的优缺点:
答:ACK机制:优点:(1)易于全面地处理丢包问题:每个成功接收的数据包都会被确认,如果发送方没有在一定时间内收到ACK,就默认出现丢包,进行重传。(2)易于保证传输正确:由于每一个数据包会对应一个ACK,只要发送方收到了所有数据包对应的ACK,就可以确定数据全部正确接收。
缺点:(1)占用流量多:由于使用发送窗口进行发送,而一旦出现错误需要退回一段距离进行重发,导致发送了大量无效数据包,占用流量。(2)效率较低:一旦数据包或ACK出现丢包,发送方就会一直等待,直到计时器超时才会重发数据,当丢包率较高,计时器的等待时间会导致运行效率低下。
NAK机制:优点:(1)占用流量少:无需对每个数据包进行返回,接收方返回NAK所需的流量远少于ACK机制下返回多个ACK所需的流量;数据包发送的数量也要少得多,大大节省了流量消耗。(2)效率高:发送方如果没接收到NAK,就可以一直发送,而如果收到NAK,也只需要回退一段距离进行发送,不存在等待计时器的逻辑,发送效率远高于ACK机制。
缺点:(1)较难处理丢包问题:如果接收方返回的NAK出现丢包,发送方将不会作出响应,只有当接收方再次返回的NAK被发送方正确接收才能进行回退操作。(2)较难判断发送结束时机:发送完最后一个数据包后,发送方并不能退出发送状态,而需一直等待。因为最后一个数据包可能丢包,接收方返回的NAK也有可能丢包,因而发送结束时机不好确定。
三、实验分析
本实验过程较长,其中出现了不少困难:
rdt3.0的实现中,出现了多线程异常导致程序运行意外终止的情况,实验数据测试只能增加实验次数以避开发生异常的情况。
任务4、5的状态机较为难懂,需要综合前三个任务的流程才可理解。
实验数据存在较大波动,需要增多测试次数才可得到更精确的结果,这部分较为麻烦,且消耗了大量时间。
四、实验总结
本实验我通过使用devcppPortable软件,在computer-networks环境下,基于给出的实验代码框架与状态机图片,补充代码实现了rdt2.1、rdt2.2、rdt3.0、基于ACK的数据传输机制和基于NAK的无连接数据传输机制,并对每种机制进行了相应测试、统计、绘图、比较与分析。圆满完成了试验任务,达成了实验目的。
五、思考题
按照RFC2018实现选择确认(SACK)机制:
①基于实验4中实现的基于ACK的GBN机制和RFC2018中选择确认机制的内容,优化ACK机制下的重传方式;
②根据任务6中完成的实验,根据实现的机制完成相同的实验并分析:分别在数据包错误、数据包丢失、数据包乱序三种情况下进行实验,对比基于ACK的GBN机制、基于ACK的选择确认机制和基于NAK的GBN机制的性能,并分析三种机制的性能差异(需要绘制与实验6中要求的相同的实验结果折线图进行实验结果分析)。
1. SACK机制的学习与理解:
SACK机制为每个数据包维护一个已接收的确认标志,并将这些标志作为SACK包发送给发送方。在接收到SACK包后,发送方可以确定哪些数据包已经被接收,哪些数据包需要重发。从而避免了大量数据包的无效重发。
2. 实现SACK机制:
在接收端,我对返回的ACK进行了改造:原先的ACK包,在数据部分并没有指定参数,我添加了一个字符串recieved_seq_temp作为发送的数据,它用于记录当前已经收到的数据包,当收到正确的数据包i(无论是否乱序),recieved_seq_temp[i]就会被置为字符’1’,表示已经收到,并将其返回发送方。
发送端:与接收端相似,也维护一个字符串,用于记录已经被确认的数据包。当收到ACK后,就将此字符串刷新。而发送时,每准备发送一个数据包,都先判断此数据包是否已经被正确接收,如果是,就跳过此数据包的发送。
3. 实验测试:
使用clumsy模拟器进行测试,参数的设置同任务6,结果如下:
图:实验数据
将其与ACK机制进行对比绘图,结果如下:
①固定丢包率与乱序率,改变数据包错误频率
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
②固定错误率与乱序率,改变数据包丢包率:
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
③固定错误率与丢包率,改变数据包乱序率:
图:两种策略下的数据包总数对比
图:两种策略下的有效吞吐量对比
4. 实验分析:
由图可见,SACK机制在以上三种情况机制中是全面优于ACK机制的。在改变丢包率与错误率的实验中,SACK机制的数据包总量总是小于ACK机制,且网络环境越差差距越大,与此同时,相同且较差环境下的SACK机制的有效吞吐量可达ACK机制的数倍。
SACK机制表现最优的情况是数据包乱序率较高的情况,由图可见数据包乱序对SACK机制的影响非常小:随着乱序率的上升,其数据包总数几乎不会增加,有效吞吐量也仅仅出现了轻微下降,相较于ACK机制有巨大优势。
5. 思考题总结:
实验过程中的数据测试往往会遇到困难,因为在丢包率、乱序率、错误率均较低的环境下,实验结果往往波动巨大,如果一次超时现象都未出现,有效吞吐量将会是一个非常大的值,这会对多次实验的平均值产生相当大的影响,即使是出现超时,超时的次数也直接决定了有效吞吐量的大小。因此,每个环境下的实验往往需要进行多次的测试,这消耗了大量时间,是实验中的难点。
思考题的编程过程也遇到了一些困难:在接收端,打包数据包的函数make_pkt()会在打包完成后使用free()释放data参数的内存,因此不可使用全局变量作为data,必须使用动态分配的内存。而实验开始时我试图直接将接收完成的数据包的编号recieved_seq发送,出现了内存访问错误,导致接收端程序意外中止,后来逐步排查,翻看头文件,才找到了问题所在,并提出解决方案。
尾注:
本实验是本课程的第三次实验,本实验的流程长度大抵是超出了合理的范畴。把每个步骤都认真完成需要几乎长达一周的时间,我的实验报告最终也超过万字。
本实验中,由于clumsy模拟器的干扰是随机产生的,如果进行重复实验,会发现实验数据的差距相当大,而为了得到较为准确的数据,也需要进行多次尝试。而本实验中需要统计的数据又过多,因此光是实验测试部分需要的时间就相当多。
如有疑问欢迎讨论,如有好的建议与意见欢迎提出,如有发现错误则恳请指正!
附:本实验所用代码
任务一:rdt2.1
客户端:
#include "rdt.h"
SOCKET sockfd; // 接收方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void receiving_packets(); // 接收数据包逻辑
double pkt_num = 0;//收到的数据包总数
double corrupt_pkt_num = 0;//收到的错误数据包数
double NAK_ACK_num = 0;//发送的ACK/NAK数
int main()
{
/******************************************初始化*******************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (SOCKADDR *)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
printf("Client init done!\n");
/*********************************************接收数据*******************************************************/
int n = 100;//实验重复次数
for (int i = 0; i < n; i++) {
receiving_packets();
}
pkt_num /= n;
corrupt_pkt_num /= n;
NAK_ACK_num /= n;
printf("\n\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
/*********************************************结束阶段*******************************************************/
closesocket(sockfd);
WSACleanup();
int xx;
scanf("%d",&xx);//保持程序不关闭
return 0;
}
/*******************************************rdt 2.1 接收方逻辑***********************************************/
void receiving_packets(){
Packet *rcvpkt;
Packet* sndpkt;
int rcv_seq = -1;
// 初始化状态
Receiver_State currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
while (TRUE)
{
switch (currentState)
{
case STATE_WAIT_FOR_EVEN_FROM_BELOW:// 等待来自下层的偶数包
printf("STATE_WAIT_FOR_EVEN_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);//接收数据包
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_even(rcvpkt))//如果数据包正确
{
extract_data(rcvpkt);//输出数据包中的data
rcv_seq = rcvpkt->seq;// 收到的数据包编号
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);//返回ACK空数据包
udt_send(sockfd, sndpkt, &server_addr);//发送
NAK_ACK_num++;
free(sndpkt);//释放空间
currentState = STATE_WAIT_FOR_ODD_FROM_BELOW;//转换状态
}
else if (corrupt(rcvpkt))//如果数据包错误(发送出错)
{
corrupt_pkt_num++;
// 打包并发送NAK包
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_NAK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
else if (notcorrupt(rcvpkt) && is_seq_odd(rcvpkt))//如果收到的是奇数包(说明丢包)
{
//此时的rcv_seq是上一次接收正确的数据包编号,也即重复发送上一次的ACK包
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_ODD_FROM_BELOW:// 等待来自下层的奇数包(内容基本同上)
printf("STATE_WAIT_FOR_ODD_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_odd(rcvpkt))
{
extract_data(rcvpkt);
rcv_seq = rcvpkt->seq;
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
}
else if (corrupt(rcvpkt))
{
corrupt_pkt_num++;
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_NAK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
else if (notcorrupt(rcvpkt) && is_seq_even(rcvpkt))
{
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
}
if (rcv_seq == TOTAL_PACKETS - 1) {
printf("\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
break;
}
}
}
服务端:
#include "rdt.h"
SOCKET sockfd; // 发送方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void sending_packets(); // 发送数据包逻辑
char *rdt_send(int number)
{
char *data = (char *)malloc(MAX_PACKET_SIZE);
// 向data中写入字符,并指明数据包的编号...
snprintf(data, MAX_PACKET_SIZE, "THIS IS DATA! Data packet number %d\0", number);
return data;
}
double corrupt_ACK_NAK_num = 0;//收到的损坏的ACK和NAK数量
double ACK_NAK_num = 0;//收到的ACK和NAK总数
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
int main()
{
/*******************************************初始化并等待通知*************************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed.\n");
closesocket(sockfd);
WSACleanup();
return 1;
}
printf("Server init done!\n");
/*********************************************发送数据********************************************************/
int n = 100;//实验重复次数
for (int i = 0; i < n; i++) {
sending_packets();
}
corrupt_ACK_NAK_num /= n;
ACK_NAK_num /= n;
pkt_num /= n;
goodput /= n;
printf("\n\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput);
/*********************************************结束阶段*******************************************************/
// 关闭套接字
closesocket(sockfd);
WSACleanup();
return 0;
}
/*******************************************rdt 2.1 发送方逻辑***********************************************/
void sending_packets()
{
// 初始化状态
Sender_State currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
// even:偶数的 等待上层偶数包
int seq = 0;//数据包编号
char* data;
Packet* rcvpkt;
boolean finish_send = FALSE;
unsigned long start_time = GetTickCount();
while (!finish_send)
{
switch (currentState)//四种状态:划分奇偶主要是为了判断是否出错
{
case STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE://等待上层偶数包
printf("STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE\n");
data = rdt_send(seq);//将数据包名字写入data中
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);//打包数据包
udt_send(sockfd, sndpkt, &client_addr);//发送数据包
pkt_num++;
seq++;//数据包编号++
currentState = STATE_WAIT_ACK_NAK_EVEN;//转换状态
break;
case STATE_WAIT_ACK_NAK_EVEN://等待ACK/NAK,偶数状态
printf("STATE_WAIT_ACK_NAK_EVEN\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);//接收ACK/NAK
ACK_NAK_num++;
if (corrupt(rcvpkt) || isNAK(rcvpkt))// 如果ACK/NAK被破坏/接收到NAK数据包
{
if (corrupt(rcvpkt)) {//ACK/NAK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);//再发一遍
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACK(rcvpkt))// (客户端)接收正确
{
free(sndpkt);//释放空间
currentState = STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE;//转移状态
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE://等待上层奇数包
printf("STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE\n");
data = rdt_send(seq);
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
seq++;
currentState = STATE_WAIT_ACK_NAK_ODD;
break;
case STATE_WAIT_ACK_NAK_ODD://等待ACK/NAK,奇数状态
printf("STATE_WAIT_ACK_NAK_ODD\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);
ACK_NAK_num++;
if (corrupt(rcvpkt) || isNAK(rcvpkt))
{
if (corrupt(rcvpkt)) {//ACK/NAK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACK(rcvpkt))
{
free(sndpkt);
currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
if (seq == TOTAL_PACKETS)//判断是否发送完毕
{
finish_send = TRUE;
unsigned long end_time = GetTickCount();
goodput += calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput);
}
}
free(rcvpkt);
}
}
}
任务二:rdt2.2
客户端:
#include "rdt.h"
SOCKET sockfd; // 接收方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void receiving_packets(); // 接收数据包逻辑
double pkt_num = 0;//收到的数据包总数
double corrupt_pkt_num = 0;//收到的错误数据包数
double NAK_ACK_num = 0;//发送的ACK/NAK数
int main()
{
/******************************************初始化*******************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (SOCKADDR *)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
printf("Client init done!\n");
/*********************************************接收数据*******************************************************/
int n = 100;//实验重复次数
for (int i = 0; i < n; i++) {
receiving_packets();
}
pkt_num /= n;
corrupt_pkt_num /= n;
NAK_ACK_num /= n;
printf("\n\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
/*********************************************结束阶段*******************************************************/
closesocket(sockfd);
WSACleanup();
int xx;
scanf("%d",&xx);//保持程序不关闭
return 0;
}
/*******************************************rdt 2.1 接收方逻辑***********************************************/
void receiving_packets(){
Packet *rcvpkt;
Packet* sndpkt;
int rcv_seq = -1;
// 初始化状态
Receiver_State currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
while (TRUE)
{
switch (currentState)
{
case STATE_WAIT_FOR_EVEN_FROM_BELOW:// 等待来自下层的偶数包
printf("STATE_WAIT_FOR_EVEN_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);//接收数据包
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_even(rcvpkt))//如果数据包正确
{
extract_data(rcvpkt);//输出数据包中的data
rcv_seq = rcvpkt->seq;// 收到的数据包编号
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);//返回ACK空数据包
udt_send(sockfd, sndpkt, &server_addr);//发送
NAK_ACK_num++;
free(sndpkt);//释放空间
currentState = STATE_WAIT_FOR_ODD_FROM_BELOW;//转换状态
}
else if (corrupt(rcvpkt)|| is_seq_odd(rcvpkt))//如果收到的是损坏的包或奇数包(说明丢包)
{
if (corrupt(rcvpkt)) {
corrupt_pkt_num++;//数据包出现损坏,统计
}
//此时的rcv_seq是上一次接收正确的数据包编号,也即重复发送上一次的ACK包
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_ODD_FROM_BELOW:// 等待来自下层的奇数包(内容基本同上)
printf("STATE_WAIT_FOR_ODD_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_odd(rcvpkt))
{
extract_data(rcvpkt);
rcv_seq = rcvpkt->seq;
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
}
else if (corrupt(rcvpkt) || is_seq_even(rcvpkt))
{
if (corrupt(rcvpkt)) {
corrupt_pkt_num++;
}
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
}
if (rcv_seq == TOTAL_PACKETS - 1) {
printf("\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
break;
}
}
}
服务端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 发送方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void sending_packets(); // 发送数据包逻辑
char *rdt_send(int number)
{
char *data = (char *)malloc(MAX_PACKET_SIZE);
// 向data中写入字符,并指明数据包的编号...
snprintf(data, MAX_PACKET_SIZE, "THIS IS DATA! Data packet number %d\0", number);
return data;
}
boolean isACKOdd(Packet* rcvpkt) {//判断ACK是否为奇数编号
return is_seq_odd(rcvpkt);
}
boolean isACKEven(Packet* rcvpkt) {//判断ACK是否为偶数编号
return is_seq_even(rcvpkt);
}
double corrupt_ACK_NAK_num = 0;//收到的损坏的ACK和NAK数量
double ACK_NAK_num = 0;//收到的ACK和NAK总数
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
int main()
{
/*******************************************初始化并等待通知*************************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed.\n");
closesocket(sockfd);
WSACleanup();
return 1;
}
printf("Server init done!\n");
/*********************************************发送数据********************************************************/
int n = 100;//实验重复次数
for (int i = 0; i < n; i++) {
sending_packets();
}
corrupt_ACK_NAK_num /= n;
ACK_NAK_num /= n;
pkt_num /= n;
goodput /= n;
printf("\n\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput);
/*********************************************结束阶段*******************************************************/
// 关闭套接字
closesocket(sockfd);
WSACleanup();
return 0;
}
/*******************************************rdt 2.1 发送方逻辑***********************************************/
void sending_packets()
{
// 初始化状态
Sender_State currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
// even:偶数的 等待上层偶数包
int seq = 0;//数据包编号
char* data;
Packet* rcvpkt;
boolean finish_send = FALSE;
unsigned long start_time = GetTickCount();
while (!finish_send)
{
switch (currentState)//四种状态:划分奇偶主要是为了判断是否出错
{
case STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE://等待上层偶数包
printf("STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE\n");
data = rdt_send(seq);//将数据包名字写入data中
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);//打包数据包
udt_send(sockfd, sndpkt, &client_addr);//发送数据包
pkt_num++;
seq++;//数据包编号++
currentState = STATE_WAIT_ACK_EVEN;//转换状态
break;
case STATE_WAIT_ACK_EVEN://等待ACK,偶数状态
printf("STATE_WAIT_ACK_EVEN\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);//接收ACK/NAK
ACK_NAK_num++;
if (corrupt(rcvpkt) || isACKOdd(rcvpkt))// 如果ACK被破坏/接收到奇数ACK
{
if (corrupt(rcvpkt)) {//ACK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);//再发一遍
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACKEven(rcvpkt))// (客户端)接收正确
{
free(sndpkt);//释放空间
currentState = STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE;//转移状态
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE://等待上层奇数包
printf("STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE\n");
data = rdt_send(seq);
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
seq++;
currentState = STATE_WAIT_ACK_ODD;
break;
case STATE_WAIT_ACK_ODD://等待ACK,奇数状态
printf("STATE_WAIT_ACK_ODD\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);
ACK_NAK_num++;
if (corrupt(rcvpkt) || isACKEven(rcvpkt)){
if (corrupt(rcvpkt)) {//ACK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACKOdd(rcvpkt)){
free(sndpkt);
currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
if (seq == TOTAL_PACKETS)//判断是否发送完毕
{
finish_send = TRUE;
unsigned long end_time = GetTickCount();
goodput += calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput);
}
}
free(rcvpkt);
}
}
}
任务三:rdt3.0
客户端:
#include "rdt.h"
SOCKET sockfd; // 接收方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void receiving_packets(); // 接收数据包逻辑
double pkt_num = 0;//收到的数据包总数
double corrupt_pkt_num = 0;//收到的错误数据包数
double NAK_ACK_num = 0;//发送的ACK/NAK数
int main()
{
/******************************************初始化*******************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (SOCKADDR *)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
printf("Client init done!\n");
/*********************************************接收数据*******************************************************/
int n = 2;//实验重复次数
for (int i = 0; i < n; i++) {
receiving_packets();
}
pkt_num /= n;
corrupt_pkt_num /= n;
NAK_ACK_num /= n;
printf("\n\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
/*********************************************结束阶段*******************************************************/
closesocket(sockfd);
WSACleanup();
int xx;
scanf("%d",&xx);//保持程序不关闭
return 0;
}
/*******************************************rdt 2.1 接收方逻辑***********************************************/
void receiving_packets(){
Packet *rcvpkt;
Packet* sndpkt;
int rcv_seq = -1;
// 初始化状态
Receiver_State currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
while (TRUE)
{
switch (currentState)
{
case STATE_WAIT_FOR_EVEN_FROM_BELOW:// 等待来自下层的偶数包
printf("STATE_WAIT_FOR_EVEN_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);//接收数据包
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_even(rcvpkt))//如果数据包正确
{
extract_data(rcvpkt);//输出数据包中的data
rcv_seq = rcvpkt->seq;// 收到的数据包编号
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);//返回ACK空数据包
udt_send(sockfd, sndpkt, &server_addr);//发送
NAK_ACK_num++;
free(sndpkt);//释放空间
currentState = STATE_WAIT_FOR_ODD_FROM_BELOW;//转换状态
}
else if (corrupt(rcvpkt)|| is_seq_odd(rcvpkt))//如果收到的是损坏的包或奇数包(说明丢包)
{
if (corrupt(rcvpkt)) {
corrupt_pkt_num++;//数据包出现损坏,统计
}
//此时的rcv_seq是上一次接收正确的数据包编号,也即重复发送上一次的ACK包
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_ODD_FROM_BELOW:// 等待来自下层的奇数包(内容基本同上)
printf("STATE_WAIT_FOR_ODD_FROM_BELOW\n");
rcvpkt = rdt_rcv(sockfd, &server_addr);
pkt_num++;
if (notcorrupt(rcvpkt) && is_seq_odd(rcvpkt))
{
extract_data(rcvpkt);
rcv_seq = rcvpkt->seq;
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
currentState = STATE_WAIT_FOR_EVEN_FROM_BELOW;
}
else if (corrupt(rcvpkt) || is_seq_even(rcvpkt))
{
if (corrupt(rcvpkt)) {
corrupt_pkt_num++;
}
sndpkt = make_pkt(rcv_seq, PACKET_TYPE_ACK, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
free(sndpkt);
}
free(rcvpkt);
break;
}
if (rcv_seq == TOTAL_PACKETS - 1) {
printf("\n客户(接收)端:\n收到的数据包总数%lf\n数据包损坏数%lf\n发送的ACK/NAK数%lf\n", pkt_num, corrupt_pkt_num, NAK_ACK_num);
break;
}
}
}
服务端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 发送方全局套接字
Packet *sndpkt; // 发送包指针(make_pkt会分配并创建)
struct sockaddr_in server_addr, client_addr; // 发送接收地址
void sending_packets(); // 发送数据包逻辑
double corrupt_ACK_NAK_num = 0;//收到的损坏的ACK和NAK数量
double ACK_NAK_num = 0;//收到的ACK和NAK总数
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
double timeout_times = 0;//超时次数
char *rdt_send(int number)
{
// 向data中写入字符,并指明数据包的编号...
char *data = (char *)malloc(MAX_PACKET_SIZE);
snprintf(data, MAX_PACKET_SIZE, "THIS IS DATA! Data packet number %d\0", number);
return data;
}
boolean isACKOdd(Packet* rcvpkt) {//判断ACK是否为奇数编号
return is_seq_odd(rcvpkt);
}
boolean isACKEven(Packet* rcvpkt) {//判断ACK是否为偶数编号
return is_seq_even(rcvpkt);
}
void CALLBACK timeout_function(void* lpParam, boolean timeout){
if(timeout){
printf("Time out!\n");
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
timeout_times++;
start_timer(timeout_function);
}
}
int main()
{
/*******************************************初始化并等待通知*************************************************/
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup failed.\n");
return 1;
}
//初始化计时器
init_timer();
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Socket creation failed.\n");
WSACleanup();
return 1;
}
// 设置服务器地址,设置本地接收
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_port = htons(CLIENT_PORT);
// 设置服务绑定
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed.\n");
closesocket(sockfd);
WSACleanup();
return 1;
}
printf("Server init done!\n");
/*********************************************发送数据********************************************************/
int n = 2;//实验重复次数
for (int i = 0; i < n; i++) {
sending_packets();
}
corrupt_ACK_NAK_num /= n;
ACK_NAK_num /= n;
pkt_num /= n;
goodput /= n;
timeout_times /= n;
printf("\n\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n超时次数:%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput, timeout_times);
/*********************************************结束阶段*******************************************************/
// 关闭套接字
closesocket(sockfd);
WSACleanup();
return 0;
}
/*******************************************rdt 2.1 发送方逻辑***********************************************/
void sending_packets()
{
// 初始化状态
Sender_State currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
// even:偶数的 等待上层偶数包
int seq = 0;//数据包编号
char* data;
Packet* rcvpkt;
boolean finish_send = FALSE;
unsigned long start_time = GetTickCount();
while (!finish_send)
{
switch (currentState)//四种状态:划分奇偶主要是为了判断是否出错
{
case STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE://等待上层偶数包
printf("STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE\n");
data = rdt_send(seq);//将数据包名字写入data中
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);//打包数据包
udt_send(sockfd, sndpkt, &client_addr);//发送数据包
pkt_num++;
seq++;//数据包编号++
currentState = STATE_WAIT_ACK_EVEN;//转换状态
start_timer(timeout_function);//开始计时
break;
case STATE_WAIT_ACK_EVEN://等待ACK,偶数状态
printf("STATE_WAIT_ACK_EVEN\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);//接收ACK/NAK
ACK_NAK_num++;
if (corrupt(rcvpkt) || isACKOdd(rcvpkt))// 如果ACK被破坏/接收到奇数ACK
{
printf("ACK error\n");
//与rdt2.2不同的是:遇到错误的返回会直接进行等待,而不是立即重发
if (corrupt(rcvpkt)) {//ACK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);//再发一遍
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACKEven(rcvpkt))// (客户端)接收正确
{
free(sndpkt);//释放空间
stop_timer();
currentState = STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE;//转移状态
}
free(rcvpkt);
break;
case STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE://等待上层奇数包
printf("STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE\n");
data = rdt_send(seq);
sndpkt = make_pkt(seq, PACKET_TYPE_DATA, data);
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
seq++;
currentState = STATE_WAIT_ACK_ODD;
start_timer(timeout_function);//开始计时
break;
case STATE_WAIT_ACK_ODD://等待ACK,奇数状态
printf("STATE_WAIT_ACK_ODD\n");
rcvpkt = rdt_rcv(sockfd, &client_addr);
ACK_NAK_num++;
if (corrupt(rcvpkt) || isACKEven(rcvpkt)){
printf("ACK error\n");
if (corrupt(rcvpkt)) {//ACK损坏
corrupt_ACK_NAK_num++;
}
udt_send(sockfd, sndpkt, &client_addr);
pkt_num++;
}
else if (notcorrupt(rcvpkt) && isACKOdd(rcvpkt)){
free(sndpkt);
stop_timer();
currentState = STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE;
if (seq == TOTAL_PACKETS)//判断是否发送完毕
{
finish_send = TRUE;
unsigned long end_time = GetTickCount();
goodput += calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n收到的ACK/NAK总数:%lf\nACK/NAK损坏数:%lf\n有效吞吐量%lf\n", pkt_num, ACK_NAK_num, corrupt_ACK_NAK_num, goodput);
}
}
free(rcvpkt);
}
}
}
任务四:ACKGBN
客户端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 客户端全局套接字
struct sockaddr_in server_addr, client_addr;
void receive_packets(); // 接收数据包逻辑
/* Functions need to be implemented */
VOID CALLBACK timer_process(PVOID lpParam, BOOLEAN TimerOrWaitFired); // 定时器到期后触发的回调函数
double NAK_ACK_num = 0;//发送的ACK/NAK数
int main()
{
/******************************************初始化*******************************************/
WSADATA wsaData;
float goodput;
WSAStartup(MAKEWORD(2, 2), &wsaData);
init_timer();//初始化计时器
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
if (bind(sockfd, (SOCKADDR*)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
receive_packets();//接收数据包
stop_timer();//停止计时器
closesocket(sockfd);
WSACleanup();
int a;
scanf("%d", &a);
return 0;
}
// 接收数据包逻辑,里面的算法可以自定义设计
void receive_packets()
{
int max_seq_received = -1; // 目前为止按序接收到的最大序号
boolean packet_received = 0; // 是否接收完成的标志
printf("Client started receiving packets...\n");
while (!packet_received)
{
Packet* rcvpkt = rdt_rcv(sockfd, &server_addr);
Packet* sndpkt = make_pkt(0, 1, NULL);
if (rcvpkt != NULL)
{
// LOOP 1: 检查是否收到的包是否发生了比特错误
if (corrupt(rcvpkt))
{
//发送上一次发送的ACK
sndpkt = make_pkt(max_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
continue;
}
// LOOP 2: 检查是否收到的包是否为按序到达的
if (rcvpkt->seq == max_seq_received + 1)
{
sndpkt = make_pkt(max_seq_received + 1, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
max_seq_received++;
if (max_seq_received == TOTAL_PACKETS - 1)
packet_received = 1;
continue;
}
// LOOP 3: 检查是否收到的包是否为乱序到达的
if (rcvpkt->seq > max_seq_received + 1)
{
sndpkt = make_pkt(max_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
continue;
}
}
else
{
printf("recv failed with error: %d\n", WSAGetLastError());
break;
}
}
printf("\n客户(接收)端:\n发送的ACK/NAK数%lf\n", NAK_ACK_num);
}
// 定时器到期后触发的回调函数
VOID CALLBACK timer_process(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
if (TimerOrWaitFired)
{
// TODO: 定时器到期处理逻辑
// Notice: 若有需要,可以重置定时器,为下一个预期的数据包启动新的定时器,如在这里继续调用 set_or_update_timer 函数
}
}
服务端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 服务器全局套接字
Packet sndpkt[TOTAL_PACKETS];
Packet* rcvpkt;
struct sockaddr_in server_addr, client_addr;
void send_packets(); // 服务器发送数据包逻辑
unsigned long WINAPI receive_naks(LPVOID lpParam); // 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
//初始化
int base = 0;//发送窗口的起始位置
int nextseqnum = 0;//下一个要发送的包的编号
int N = 10;//发送窗口长度
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
unsigned long start_time;
unsigned long end_time;
void CALLBACK timeout_function(void* lpParam, boolean timeout) {//超时情况
if (timeout && base < TOTAL_PACKETS - 1) {
start_timer(timeout_function);
printf("Time out! resend:%d~%d\n", base, nextseqnum - 1);
//timeout_times++;
for (int i = base; i < nextseqnum; i++) {
udt_send(sockfd, &sndpkt[i], &client_addr);
pkt_num++;
}
}
}
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(CLIENT_PORT);
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d\n", WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return -1;
}
//初始化计时器
init_timer();
// 创建接收线程用以监听收到的NAK包并处理
HANDLE receive_thread = CreateThread(NULL, 0, receive_naks, &sockfd, 0, NULL);
start_time = GetTickCount();
// 发送数据
send_packets();
// 等待线程完成
WaitForSingleObject(receive_thread, INFINITE);
closesocket(sockfd);
WSACleanup();
return 0;
}
// 服务器发送数据包逻辑
void send_packets()
{
printf("Server started sending packets...\n");
for (int i = 0; i < TOTAL_PACKETS; ++i)
{
// 预先装填所有数据,每个数据包大小固定为MAX_PACKET_SIZE,请勿修改
int written = snprintf(sndpkt[i].data, MAX_PACKET_SIZE, "Packet %d", i);//将字符串"Packet %d"写入到sndpkt[i].data中
// 将sndpkt[i].data数组中剩下未使用的部分填充为字符'A',保留1字节给结尾的空字符
memset(sndpkt[i].data + written, 'A', MAX_PACKET_SIZE - written - 1);
sndpkt[i].type = 0;
sndpkt[i].seq = i;
sndpkt[i].checksum = calculate_checksum(&sndpkt[i]);
}
while (nextseqnum < TOTAL_PACKETS) {//循环判断
if (nextseqnum < base + N) {
udt_send(sockfd, &sndpkt[nextseqnum], &client_addr);//发送数据包
pkt_num++;
if (base == nextseqnum) {//如果发送的是窗口中的第一个数据包
start_timer(timeout_function);//启动计时器
}
nextseqnum++;
}
}
printf("Server finished sending all packets.\n");
}
// 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
unsigned long receive_naks(LPVOID pl_param)
{
// 服务器应一直监听来自客户端的数据包
while (base < TOTAL_PACKETS)
{
rcvpkt = rdt_rcv(sockfd, &client_addr);
if (corrupt(rcvpkt)) {//ACK损坏
}
else {
base = rcvpkt->seq + 1;
// printf("%d\n", base); // 输出base,测试用
if (base == nextseqnum) {
stop_timer();
}
else {
start_timer(timeout_function);
}
if(base == TOTAL_PACKETS - 1){
end_time = GetTickCount();
goodput = calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n有效吞吐量%lf\n", pkt_num, goodput);
}
}
// TODO: 收到NAK处理重发
}
}
任务五:NAKGBN
客户端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 客户端全局套接字
struct sockaddr_in server_addr, client_addr;
void receive_packets(); // 接收数据包逻辑
/* Functions need to be implemented */
double NAK_ACK_num = 0;//发送的ACK/NAK数
int max_seq_received = -1; // 目前为止按序接收到的最大序号
VOID CALLBACK timer_process(PVOID lpParam, BOOLEAN TimerOrWaitFired); // 定时器到期后触发的回调函数
void CALLBACK timeout_function(void* lpParam, boolean timeout) {//超时情况
if (timeout)
{
// TODO: 定时器到期处理逻辑
printf("time out!\n");
Packet* sndpkt;
sndpkt = make_pkt(max_seq_received + 1, -1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
start_timer(timeout_function);
// Notice: 若有需要,可以重置定时器,为下一个预期的数据包启动新的定时器,如在这里继续调用 set_or_update_timer 函数
}
}
int main()
{
/******************************************初始化*******************************************/
WSADATA wsaData;
float goodput;
WSAStartup(MAKEWORD(2, 2), &wsaData);
init_timer();//初始化计时器
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
if (bind(sockfd, (SOCKADDR*)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
receive_packets();//接收数据包
closesocket(sockfd);
WSACleanup();
int a;
scanf("%d", &a);
return 0;
}
// 接收数据包逻辑,里面的算法可以自定义设计
void receive_packets()
{
boolean packet_received = 0; // 是否接收完成的标志
max_seq_received = -1;
printf("Client started receiving packets...\n");
while (!packet_received)
{
Packet* rcvpkt = rdt_rcv(sockfd, &server_addr);
Packet* sndpkt;
if (rcvpkt != NULL)
{
// 数据包出错/乱序
if (corrupt(rcvpkt) || rcvpkt->seq > max_seq_received + 1)
{
//发送NAK,编号为期望接到的数据包编号
sndpkt = make_pkt(max_seq_received + 1, -1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
start_timer(timeout_function);
}
// 数据包正确
else if (rcvpkt->seq == max_seq_received + 1)
{
max_seq_received++;
start_timer(timeout_function);
if (max_seq_received == TOTAL_PACKETS - 1){//接收结束
stop_timer();
printf("recieve complete!\n");
packet_received = 1;
sndpkt = make_pkt(max_seq_received + 1, 1, NULL);
//发送一系列ACK,此处我设置为10个
for (int i = 10; i > 0; i--)udt_send(sockfd, sndpkt, &server_addr);
}
}
}
else
{
printf("recv failed with error: %d\n", WSAGetLastError());
break;
}
}
printf("\n客户(接收)端:\n发送的ACK/NAK数%lf\n", NAK_ACK_num);
}
// 定时器到期后触发的回调函数
VOID CALLBACK timer_process(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
if (TimerOrWaitFired)
{
// TODO: 定时器到期处理逻辑
printf("time out!\n");
Packet* sndpkt;
sndpkt = make_pkt(max_seq_received + 1, -1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
start_timer(timeout_function);
// Notice: 若有需要,可以重置定时器,为下一个预期的数据包启动新的定时器,如在这里继续调用 set_or_update_timer 函数
}
}
服务端:
#include "rdt.h"
SOCKET sockfd; // 服务器全局套接字
Packet sndpkt[TOTAL_PACKETS];
Packet* rcvpkt;
struct sockaddr_in server_addr, client_addr;
void send_packets(); // 服务器发送数据包逻辑
unsigned long WINAPI receive_naks(LPVOID lpParam); // 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
//初始化
int nextseqnum = 0;//下一个要发送的包的编号
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
unsigned long start_time;
unsigned long end_time;
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(CLIENT_PORT);
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d\n", WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return -1;
}
// 创建接收线程用以监听收到的NAK包并处理
HANDLE receive_thread = CreateThread(NULL, 0, receive_naks, &sockfd, 0, NULL);
// 发送数据
send_packets();
// 等待线程完成
WaitForSingleObject(receive_thread, INFINITE);
closesocket(sockfd);
WSACleanup();
return 0;
}
// 服务器发送数据包逻辑
void send_packets()
{
start_time = GetTickCount();
printf("Server started sending packets...\n");
for (int i = 0; i < TOTAL_PACKETS; ++i)
{
// 预先装填所有数据,每个数据包大小固定为MAX_PACKET_SIZE,请勿修改
int written = snprintf(sndpkt[i].data, MAX_PACKET_SIZE, "Packet %d", i);//将字符串"Packet %d"写入到sndpkt[i].data中
// 将sndpkt[i].data数组中剩下未使用的部分填充为字符'A',保留1字节给结尾的空字符
memset(sndpkt[i].data + written, 'A', MAX_PACKET_SIZE - written - 1);
sndpkt[i].type = 0;
sndpkt[i].seq = i;
sndpkt[i].checksum = calculate_checksum(&sndpkt[i]);
}
while (1) {//循环判断
if (nextseqnum < TOTAL_PACKETS) {
udt_send(sockfd, &sndpkt[nextseqnum], &client_addr);//发送数据包
pkt_num++;
nextseqnum++;
}
else if(nextseqnum == TOTAL_PACKETS + 1){
break;
}
}
printf("Server finished sending all packets.\n");
}
// 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
unsigned long receive_naks(LPVOID pl_param)
{
// 服务器应一直监听来自客户端的数据包
int flag = 0;
while (1)
{
rcvpkt = rdt_rcv(sockfd, &client_addr);
if(nextseqnum!=TOTAL_PACKETS + 1)flag=0;
if (isNAK(rcvpkt) && notcorrupt(rcvpkt)) {//NAK未损坏
nextseqnum = rcvpkt->seq - 1;
// printf("%d\n", base); // 输出base,测试用
}
else if (isACK(rcvpkt)&& !flag) {//收到ACK,发送结束
end_time = GetTickCount();
goodput += calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n有效吞吐量%lf\n", pkt_num, goodput);
nextseqnum = TOTAL_PACKETS + 1;
flag=1;
}
}
}
思考题:SACK
客户端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 客户端全局套接字
struct sockaddr_in server_addr, client_addr;
void receive_packets(); // 接收数据包逻辑
/* Functions need to be implemented */
VOID CALLBACK timer_process(PVOID lpParam, BOOLEAN TimerOrWaitFired); // 定时器到期后触发的回调函数
char recieved_seq[TOTAL_PACKETS];//已收到的数据包对应编号的位置会被改为(字符)1
double NAK_ACK_num = 0;//发送的ACK/NAK数
char* recieved_seq_temp;
int max_correct_seq_received = -1; // 目前为止按序接收到的最大序号
void CALLBACK timeout_function(void* lpParam, boolean timeout) {//超时情况
/*if (timeout) {
start_timer(timeout_function);
recieved_seq_temp = (char*) malloc(TOTAL_PACKETS);
printf("Time out!\n");
//发送上一次发送的ACK
Packet* sndpkt = make_pkt(max_correct_seq_received, 1, recieved_seq_temp);
//sndpkt = make_pkt(max_correct_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
}*/
}
int main()
{
/******************************************初始化*******************************************/
WSADATA wsaData;
float goodput;
WSAStartup(MAKEWORD(2, 2), &wsaData);
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
client_addr.sin_port = htons(CLIENT_PORT);
//初始化计时器
init_timer();
//初始化每个数据包的接收状态
for (int i = 0; i < TOTAL_PACKETS; i++)recieved_seq[i] = '0';
if (bind(sockfd, (SOCKADDR*)&client_addr, sizeof(client_addr)) == SOCKET_ERROR)
{
printf("Bind failed\n");
closesocket(sockfd);
WSACleanup();
return -1;
}
receive_packets();//接收数据包
closesocket(sockfd);
WSACleanup();
int a;
scanf("%d", &a);
return 0;
}
// 接收数据包逻辑
void receive_packets()
{
boolean packet_received = 0; // 是否接收完成的标志
printf("Client started receiving packets...\n");
while (!packet_received)
{
recieved_seq_temp = (char*) malloc(TOTAL_PACKETS);
memcpy(recieved_seq_temp, recieved_seq, TOTAL_PACKETS);
Packet* rcvpkt = rdt_rcv(sockfd, &server_addr);
printf("收到!编号:%d max_correct_seq_received:%d\n",rcvpkt->seq, max_correct_seq_received);
start_timer(timeout_function);
Packet* sndpkt;
//Packet* sndpkt = make_pkt(0, 1, NULL);
if (rcvpkt != NULL)
{
//数据包出错
if (corrupt(rcvpkt))
{
//发送上一次发送的ACK
sndpkt = make_pkt(max_correct_seq_received, 1, recieved_seq_temp);
//sndpkt = make_pkt(max_correct_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
}
//乱序数据包:也有效!
else if (rcvpkt->seq > max_correct_seq_received + 1) {
//printf("乱序数据包!,编号:%d\n",rcvpkt->seq);
recieved_seq[rcvpkt->seq] = '1';
sndpkt = make_pkt(max_correct_seq_received, 1, recieved_seq_temp);
//sndpkt = make_pkt(max_correct_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
//printf("乱序数据包返回成功\n");
NAK_ACK_num++;
}
//正确数据包
else if (rcvpkt->seq == max_correct_seq_received + 1)
{
recieved_seq[rcvpkt->seq] = '1';
while (recieved_seq[max_correct_seq_received + 1] == '1') {//处理编号
max_correct_seq_received++;
}
sndpkt = make_pkt(max_correct_seq_received, 1, recieved_seq_temp);
//sndpkt = make_pkt(max_correct_seq_received, 1, NULL);
udt_send(sockfd, sndpkt, &server_addr);
NAK_ACK_num++;
if(max_correct_seq_received > TOTAL_PACKETS - 5)
printf("\n客户(接收)端:\n发送的ACK/NAK数%lf\n", NAK_ACK_num);
if (max_correct_seq_received == TOTAL_PACKETS - 1)
packet_received = 1;
}
//重复数据包
else if (rcvpkt->seq < max_correct_seq_received + 1) {
//printf("重复数据包!,编号:%d\n",rcvpkt->seq);
sndpkt = make_pkt(max_correct_seq_received, 1, recieved_seq_temp);
udt_send(sockfd, sndpkt, &server_addr);
//printf("重复数据包返回成功\n");
NAK_ACK_num++;
}
}
else
{
printf("recv failed with error: %d\n", WSAGetLastError());
break;
}
}
printf("\n客户(接收)端:\n发送的ACK/NAK数%lf\n", NAK_ACK_num);
}
服务端:
#include "rdt.h"
#include "timer.h"
SOCKET sockfd; // 服务器全局套接字
Packet sndpkt[TOTAL_PACKETS];
Packet* rcvpkt;
struct sockaddr_in server_addr, client_addr;
void send_packets(); // 服务器发送数据包逻辑
unsigned long WINAPI receive_naks(LPVOID lpParam); // 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
//初始化
int base = 0;//发送窗口的起始位置
int nextseqnum = 0;//下一个要发送的包的编号
int N = 10;//发送窗口长度
char recieved_seq[TOTAL_PACKETS];//已确认收到的数据包对应编号的位置会被改为(字符)1
double pkt_num = 0;//发送的数据包总数
double goodput = 0;//有效吞吐量
unsigned long start_time;
unsigned long end_time;
int changing_flag=0;//表示记录数组是否正在被改变
void CALLBACK timeout_function(void* lpParam, boolean timeout) {//超时情况
if (timeout && base < TOTAL_PACKETS - 1) {
start_timer(timeout_function);
printf("Time out! resend:%d~%d\n", base, nextseqnum - 1);
//timeout_times++;
for (int i = base; i < nextseqnum; i++) {
if (recieved_seq[nextseqnum] == '1') {
continue;
}
udt_send(sockfd, &sndpkt[i], &client_addr);
pkt_num++;
}
}
}
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET)
{
printf("Failed to create socket\n");
WSACleanup();
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(CLIENT_PORT);
client_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d\n", WSAGetLastError());
closesocket(sockfd);
WSACleanup();
return -1;
}
//初始化计时器
init_timer();
//初始化接收判断数组
for (int i = 0; i < TOTAL_PACKETS; i++)recieved_seq[i] = '0';
// 创建接收线程用以监听收到的NAK包并处理
HANDLE receive_thread = CreateThread(NULL, 0, receive_naks, &sockfd, 0, NULL);
start_time = GetTickCount();
// 发送数据
send_packets();
// 等待线程完成
WaitForSingleObject(receive_thread, INFINITE);
closesocket(sockfd);
WSACleanup();
return 0;
}
// 服务器发送数据包逻辑
void send_packets()
{
printf("Server started sending packets...\n");
for (int i = 0; i < TOTAL_PACKETS; ++i)
{
// 预先装填所有数据,每个数据包大小固定为MAX_PACKET_SIZE,请勿修改
int written = snprintf(sndpkt[i].data, MAX_PACKET_SIZE, "Packet %d", i);//将字符串"Packet %d"写入到sndpkt[i].data中
// 将sndpkt[i].data数组中剩下未使用的部分填充为字符'A',保留1字节给结尾的空字符
memset(sndpkt[i].data + written, 'A', MAX_PACKET_SIZE - written - 1);
sndpkt[i].type = 0;
sndpkt[i].seq = i;
sndpkt[i].checksum = calculate_checksum(&sndpkt[i]);
}
while (nextseqnum < TOTAL_PACKETS) {//循环判断
if (nextseqnum < base + N&&changing_flag==0) {//数组正在被改变时不要发送数据
if (recieved_seq[nextseqnum] == '1') {
continue;
}
udt_send(sockfd, &sndpkt[nextseqnum], &client_addr);//发送数据包
pkt_num++;
if (base == nextseqnum) {//如果发送的是窗口中的第一个数据包
start_timer(timeout_function);//启动计时器
}
nextseqnum++;
}
}
printf("Server finished sending all packets.\n");
}
// 服务器新建线程接收NAK包逻辑,因为使用线程库,所以返回值为DWORD
unsigned long receive_naks(LPVOID pl_param)
{
// 服务器应一直监听来自客户端的数据包
while (base < TOTAL_PACKETS)
{
rcvpkt = rdt_rcv(sockfd, &client_addr);
if (corrupt(rcvpkt)) {//ACK损坏
}
else {
base = rcvpkt->seq + 1;//前移窗口
if(rcvpkt->data){
changing_flag=1;
//printf("起点:%d ",base);
for (int i = base; i < TOTAL_PACKETS; i++) {
//printf("%c",rcvpkt->data[i]);
if (rcvpkt->data[i] == '1') {
recieved_seq[i] = '1';
}
}
changing_flag=0;
//printf("\n");
}
// printf("%d\n", base); // 输出base,测试用
if (base == nextseqnum) {
stop_timer();
}
else {
start_timer(timeout_function);
}
if(base > TOTAL_PACKETS - 3){
end_time = GetTickCount();
goodput = calculate_goodput(start_time, end_time);
printf("\n服务(发送)端:\n发送的数据包总数:%lf\n有效吞吐量%lf\n", pkt_num, goodput);
}
}
}
}
有修改的头文件:
rdt.h:
#pragma once
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define LOCAL_IP "127.0.0.1"
#define SERVER_PORT 12345
#define CLIENT_PORT 12346
#define MAX_PACKET_SIZE 1024
#define TOTAL_PACKETS 200
// 接收方状态
typedef enum
{
STATE_WAIT_FOR_EVEN_FROM_BELOW,
STATE_WAIT_FOR_ODD_FROM_BELOW
} Receiver_State;
// 发送方状态
typedef enum
{
STATE_WAIT_FOR_CALL_EVEN_FROM_ABOVE,
STATE_WAIT_FOR_CALL_ODD_FROM_ABOVE,
STATE_WAIT_ACK_EVEN,
STATE_WAIT_ACK_ODD,
} Sender_State;
// 数据包定义
typedef enum
{
PACKET_TYPE_ACK = 1,
PACKET_TYPE_DATA = 0,
PACKET_TYPE_NAK = -1
} Packet_Type;
typedef struct
{
int seq;
Packet_Type type; // -1 0 1
unsigned int checksum; // 新增加的校验和字段
char data[MAX_PACKET_SIZE];
} Packet;
unsigned int calculate_checksum(Packet *pkt)//计算一个数据包的校验和,用于检测数据包在传输过程中是否发生了错误
{
unsigned int checksum = 0;
unsigned char *bytes = (unsigned char *)pkt;
for (int i = 0; i < sizeof(Packet); i++)
{
// 跳过checksum字段本身的计算
if (bytes + i >= (unsigned char *)&pkt->checksum &&
bytes + i < (unsigned char *)&pkt->checksum + sizeof(pkt->checksum))
{
continue;
}
checksum += bytes[i];
}
return checksum;
}
// 将三个内容打包成一个数据包
Packet* make_pkt(int seq, Packet_Type type, char *data)
{
Packet* packet = (Packet*)malloc(sizeof(Packet));
packet->seq = seq;
packet->type = type;
if (data == NULL)
{
data = (char *)malloc(MAX_PACKET_SIZE);
data[0] = '\0';
}
memcpy(packet->data, data, MAX_PACKET_SIZE);
free(data);
packet->checksum = calculate_checksum(packet);
return packet;
}
// unreliable data transfer send
// 将数据包packet经过套接字sockfd发送到指定的地址addr
void udt_send(SOCKET sockfd, Packet *packet, struct sockaddr_in *addr)
{
char *buffer = (char *)malloc(sizeof(Packet));
memcpy(buffer, packet, sizeof(Packet));
// 发送通知
if (sendto(sockfd, buffer, sizeof(Packet), 0, (struct sockaddr *)addr, sizeof(*addr)) == SOCKET_ERROR)
{
printf("Error code : %d\n", WSAGetLastError());
printf("Sendto failed.\n");
}
else
{
printf("Sent successfully. Sequence: %d; Type:%d.\n", packet->seq, packet->type);
}
free(buffer);
}
// 通过套接字sockfd接收来自指定地址addr的数据包
Packet *rdt_rcv(SOCKET sockfd, struct sockaddr_in *addr)
{
char *buffer = (char *)malloc(sizeof(Packet));
int addr_size = sizeof(*addr);
if (recvfrom(sockfd, buffer, sizeof(Packet), 0, (struct sockaddr *)addr, &addr_size) == SOCKET_ERROR)
{
printf("Error code : %d\n", WSAGetLastError());
printf("Recvfrom failed.\n");
return NULL;
}
return (Packet *)buffer;
}
// 判断接收到的数据包是否被破坏
boolean corrupt(Packet *rcvpkt)
{
if (rcvpkt->checksum != calculate_checksum(rcvpkt))
{
printf("Packet corrupted!\n");
return TRUE;
}
else
{
return FALSE;
}
}
//检查接收到的数据包是否被破坏(返回值与corrupt相反)
boolean notcorrupt(Packet *rcvpkt)
{
if (rcvpkt->checksum == calculate_checksum(rcvpkt))
{
return TRUE;
}
else
{
return FALSE;
}
}
//检查接收到的数据包类型是否为ACK(确认)
boolean isACK(Packet *rcvpkt)
{
if (rcvpkt->type == PACKET_TYPE_ACK)
{
printf("Received ACK of %d\n", rcvpkt->seq);
return TRUE;
}
return FALSE;
}
//检查接收到的数据包类型是否为NAK(未确认)
boolean isNAK(Packet *rcvpkt)
{
if (rcvpkt->type == PACKET_TYPE_NAK)
{
printf("Received NAK of %d\n", rcvpkt->seq);
return TRUE;
}
return FALSE;
}
//判断数据包的编号是否为偶数
boolean is_seq_even(Packet *rcvpkt)
{
if (rcvpkt->seq % 2 == 0)
return TRUE;
return FALSE;
}
//判断数据包的编号是否为奇数
boolean is_seq_odd(Packet *rcvpkt)
{
return !is_seq_even(rcvpkt);
}
//输出数据包中的数据
void extract_data(Packet *packet)
{
printf("Received data: %s\n", packet->data);
return;
}
// 计算Goodput
// 自行考虑start_time 和 end_time的记录时间
// 调用方式 start_time = GetTickCount();
float calculate_goodput(unsigned long start_time, unsigned long end_time)
{
unsigned long long total_bytes_received = MAX_PACKET_SIZE * TOTAL_PACKETS; // 接收到的总字节数
float goodput = (float)total_bytes_received / (float)(end_time - start_time) * 1000.0f; // bytes per second
printf("Total time elapsed: %lu ms\n", (end_time - start_time));
printf("Goodput: %f B/s\n", goodput);
return goodput;
}
timer.h:
#include <winsock2.h>
#include <stdio.h>
#define TIMEOUT_SECOND 1
UINT_PTR timerId = 0; // 全局定时器ID
HANDLE hTimer = NULL;
HANDLE hTimerQueue = NULL;
int init_timer()
{
hTimerQueue = CreateTimerQueue();
if (hTimerQueue == NULL)
{
int error = GetLastError();
printf("Create timer queue failed (%d)\n", error);
return error;
}
else
return 0;
}
// 用于启动或重置定时器的函数
void set_or_update_timer(WAITORTIMERCALLBACK timeout_function_name, int timeout_millisecond)
{
// 如果已经有一个定时器在队列中,先删除它
if (hTimer != NULL)
{
DeleteTimerQueueTimer(hTimerQueue, hTimer, NULL);
hTimer = NULL; // 确保我们不会再次尝试删除它
}
// 创建或重新设置定时器
if (!CreateTimerQueueTimer(&hTimer, hTimerQueue, timeout_function_name, NULL, timeout_millisecond, 0, WT_EXECUTEONLYONCE))
{
printf("Create timer failed (%d)\n", GetLastError());
}
}
// 直接启动一个超时为1s的定时器,参数为计时器函数的名字
void start_timer(WAITORTIMERCALLBACK timeout_function_name)
{
set_or_update_timer(timeout_function_name, TIMEOUT_SECOND * 1000);
}
// 停止定时器
void stop_timer()
{
if (hTimer != NULL)//计时器存在
{
if (!DeleteTimerQueueTimer(hTimerQueue, hTimer, INVALID_HANDLE_VALUE))
{//使用删除函数的返回值判断删除是否失败:1成功;0失败
printf("Stop timer failed (%d)\n", GetLastError());
}
hTimer = NULL;
}
else//计时器不存在
{
printf("Trying to stop non-existing timer?");
}
}
// 需要在实现后调用SetOrUpdateTimer,参数就是这个函数的名字
void CALLBACK timeout_function(void* lpParam, boolean timeout);// STUDENT?:必须实现的函数
// 示例:
void CALLBACK timeout_function_example(void *lpParam, boolean timeout)
{
// 第一个参数必须为void* lpParam,但是基本用不到
// 第二个参数是触发事件的判断条件,例如
if (timeout)
{
// 定时器到期处理逻辑
printf("Timer expired!\n");
}
}