实验内容
- 基于UDP设计一个简单的停等和GBN协议,实现单向可靠数据传输(服务器到客户的数据传输);
- 模拟引入数据包的丢失,验证所设计协议的有效性;
- 基于所设计的停等协议,实现一个C/S结构的文件传输应用。
- 改进所设计的GBN协议,支持双向数据传输;
- 将所设计的GBN协议改进为SR协议。
一、GBN协议可靠数据传输
当设置服务器端发送窗口的大小为 1 时, GBN 协议就是停-等协议。
1.服务器端Server
使用UDP协议传输数据(比如传输一个文件),等待客户端的请求,接收并处理来自客户端的消息(如数据传输请求)。
在服务端运行之后,首先会初始化套接字并绑定端口地址监听(非阻塞接收),监听到客户端发送的命令执行函数,当接收到“-time”或者“-quit”报文时,则执行返回时间并打包发给客户端和退出程序。
当客户端开始请求数据,即服务器端接收到“-testgbn”报文的话,就会进入握手阶段,建立连接状态后(并不是真正的连接,只是一种类似连接的数据发送的状态),将数据打包成数据报文发送,然后等待客户端的ACK信息,同时启动计时器。
握手阶段:首先服务器端(服务器端处于0状态)向客户端发送205状态码(服务器端进入1状态),而后服务器端调用recvfrom()等待客户端回复200状态码。
如果收到(服务器端进入2状态),初始化现在的序列号curSeq,现在已确认报文序列号curAck,计时器waitCount,开始传输文件;否则延时等待直至超时,超时则放弃此次“连接”,等待从第一步开始。
传输阶段:服务器端首先将数据打包成数据报文,调用函数seqIsAvailable()函数判断序列号是否在当前发送窗口之内,然后按数据帧格式将数据包发送给客户端。发完之后curSeq加1并对SEQ_SIZE取模得到新的curSeq。
而后调用recvfrom()非阻塞等待接受ACK。当收到ACK时,调用ackHandler()函数进行窗口滑动(累积确认),正常发送下一个数据报,计时器重新计时;若在计时器超时前没有收到ACK,则调用超时重传函数timeoutHandler()全部重传窗口内的所有已发送的数据报。
计时器的实现方式为:设置套接字为非阻塞模式,服务器端在recvfrom()方法上不会阻塞。如果正确接收到ACK消息,那么计时器将被清零。但如果从客户端接收到数据长度为-1,这表示没有接收到任何数据,那么计时器将增加。对计时器进行判断,如果计时器超过了阈值,就被认为发生了超时,此时会执行超时重传操作。
最后当序列号curSeq等于数据发送的总包数totalPacket时停止发送新的报文,而当收到的ACK等于总包数totalPacket时传输结束,并向客户端发送状态码同步传输结束。
2.客户端Client
使用UDP协议向服务器端请求数据,接收服务器端发送的数据报并返回确认信息ACK(注意GBN为累积确认,即客户端返回当前接受有序最新数据包的确认ACK)。
客户端首先初始化套接字并绑定端口地址监听(阻塞接收),使用命令行输入指令,当输入为“-time”或者“-quit”则直接作为数据包发送给服务端(查询时间以及退出)。
如果输入为“-testgbn_recv [X][Y]”,则进入等待握手阶段(客户端处于0状态),当收到来自服务器的205状态码后,向服务器发送200状态码,同时初始化已收到的序列号recvSeq为0和等待收到的序列号waitSeq为1,进入等待传输阶段(1状态)
而后调用recvfrom()等待来自服务器的数据报文,如果是期待的包,正确接收,正常确认即可;否则就丢弃此数据包,返回上一个正确接收的包的序列号recvSeq的ACK。如果当前一个包都没有收到,则等待Seq为1的数据包,不是则不返回ACK(因为并没有上一个正确的ACK)。
最后当接收到服务器245状态码时(表示传输完毕)结束接收,等待下一条用户指令的输入。同时清除接收缓存RecvMessage和报文缓存buffer,避免下次传输出错。
二、模拟数据包丢失
在客户端中引入默认包丢失率packetLossRatio和默认 ACK 丢失率ackLossRatio来模拟引入数据包的丢失和ACK丢失的情况。
模拟的思路是:引入b作为随机变量,其值对应lossInLossRatio()函数的返回值,当接受到报文和发送ACK之前均对b进行判断,如若为TRUE,则跳过当前接受数据循环,直接进入下一次循环,在阻塞态下,即为接受下一个数据包;反之则正常进行数据接受和反馈。
如若报文丢失,recvSeq和waitSeq不会变化;ACK丢失,recvSeq和waitSeq正常移动加一。但两种情况下,后续服务器会进行超时重传处理。
lossInLossRatio()函数根据丢失率随机生成一个数字,判断是否丢失,如若丢失则返回TRUE,否则返回FALSE。
三、基于C/S结构的文件传输
为实现基于C/S结构的文件传输,服务端首先要将测试文件test.txt内容读入拷贝到缓存,计算出要发送的总包数totalPacket,而后将序号组的ACK设为TURE(表明该分组还没发送成功即没有收到相应ACK)。
客户端在收到服务端发送的数据包之后,如果是期望收到的,则将收到的数据缓存到recvMessage中。在接收到245状态码结束传输之后写入文本文件output.txt。
为实现基于C/S结构的文件传输,服务端首先要将测试文件test.txt内容读入拷贝到缓存,计算出要发送的总包数totalPacket,而后将序号组的ACK设为TURE(表明该分组还没发送成功即没有收到相应ACK)。
客户端在收到服务端发送的数据包之后,如果是期望收到的,则将收到的数据缓存到recvMessage中。在接收到245状态码结束传输之后写入文本文件output.txt。
四、双向数据传输
为实现双向数据传输,在客户端界面上增加一个选项为“-testgbn_send”,输入后客户端会向服务器端发送“-receive”报文,而后将扮演服务器的角色项服务器端发送数据。经历握手、数据传输等阶段。
客户端收到“-receive”报文后,则进入接收信息的状态,经历等待握手、等待数据传输、数据传输完毕的各个阶段。
总体上看,就是合并了客户端和服务端的代码。需要注意的即是不用换用两个套接字,但是需要在调换角色的同时更改套接字的阻塞状态(即作为服务器端需要非阻塞态,客户端采用阻塞态)。
五、SR协议的改进
1.服务器端Server
SR协议的服务端和GBN协议的服务端的最主要区别在于,GBN只有当收到对应当前发送窗口最左侧send_base数据包的ACK时才会处理并进行窗口滑动。
而SR可以收到并处理对应发送窗口(send_base到send_base+SEQ_SIZE)内所有数据包的ACK,而当收到对应当前发送窗口最左侧send_base数据包的ACK时进行窗口滑动直至第一个未确认接收ACK的位置。
在接受ACK阶段,如果发生了丢包或者其他错误,程序会遍历期望收到的ACK,查看哪些ACK是还没有收到(即没有被确认),然后对应分组i的计时器++,超过8次则重新传送分组。
而如果收到ACK,则会对第i个分组的ACK位置标志设为TRUE(在读入数据时将所有ACK设为FALSE以区分是否接收),同时窗口基址send_base向右移动到当前未确认的第一个位置。
而在结束传输上,与GBN类似,当发送包序列号超过总包数totalPacket时就拒绝发送,而当发送窗口移动到totalPacket右侧时即传输结束。
2.客户端Client
SR协议的客户端和GBN协议的客户端的区别在于GBN只能接受当前等待接收的数据包(接收一定有序)同时接受窗口向右移动,否则就丢弃。
而SR客户端可以接受在接收窗口内任意的数据包(接收不一定有序),同样只有当收到对应当前发送窗口最左侧recv_base数据包时才会进行窗口滑动,滑动到当前未接收的第一个序列位置。
具体实现上,同样设置一个接收序列数组recvack[],如果收到预期消息,接收窗口右移;否则就将接受位置对应标志设为TRUE,缓存到recvMessage对应的位置中。同样采用默认包丢失率packetLossRatio和默认 ACK 丢失率ackLossRatio来模拟引入数据包的丢失和ACK丢失的情况。
3.窗口大小
滑动窗口协议中发送/接收窗口大小的设置:设序列号位数为n,发送窗口大小N_send,接收窗口为N_recv,则满足N_send+N_recv≤2^n。SR协议中一般N_send=N_recv,原因是发送窗口大于接收窗口会导致溢出,小于则没有意义。
本实验中SR协议窗口大小均设置为8。