南京大学本科生实验报告¶
课程名称: 计算机网络 任课教师:田臣
学院 | 工程管理学院 | 专业(方向) | 计算机金融 |
---|---|---|---|
学号 | 211275022 | 姓名 | 田昊东 |
1040880153@qq.com | 开始/完成日期 | 2023/5/28~2023/5/28 |
实验名称¶
Lab 6: Reliable Communication¶
实验目的¶
- 设计一个由三部分:发送方、接受方、中间盒组成的可靠通信协议
- 改变参数的值观察结果,分析传输效率的影响因素
实验内容¶
Task 2: Middlebox¶
-
python def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): _, fromIface, packet = recv if fromIface == "middlebox-eth0": print("Received from blaster") num = random.random() #丢包 if num < self.dropRate: print("drop ", num) return #不丢包,修改包头 eternet = packet.get_header(Ethernet) eternet.src = "40:00:00:00:00:02" eternet.dst = "20:00:00:00:00:01" self.net.send_packet("middlebox-eth1", packet) elif fromIface == "middlebox-eth1": print("Received from blastee") ipv4 = packet.get_header(IPv4) eternet = packet.get_header(Ethernet) ipv4.ttl = ipv4.ttl - 1 eternet.src = "40:00:00:00:00:01" eternet.dst = "10:00:00:00:00:01" self.net.send_packet("middlebox-eth0", packet)
-
这部分的思路比较简单,只需要对两个方向的包做区分处理
- 对于来自blaster的包,有一个概率会将包随机丢弃,模拟实际防落环境中可能出现的各种问题
-
对于来自blastee的包,保证正常传输
-
此外需要修改包的以太网包头,重新设置src和dst
- 最后将包从另一个端口传输出去
Task 3: Blastee¶
-
python def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): _, fromIface, packet = recv print(f"I got a packet from {fromIface}") #包头初始化 reply = Ethernet()+IPv4()+UDP() reply[0].src = "20:00:00:00:00:01" reply[0].dst = "40:00:00:00:00:02" reply[1].dst = self.blasterIp reply[1].src = "192.168.200.1" reply[1].ttl = 64 reply[1].protocol = IPProtocol.UDP reply[2].src = 8080 reply[2].dst = 8080 #内容初始化 receive = packet[RawPacketContents].to_bytes() num = receive[:4] payload = receive[6:14] #填充字节 if len(payload) < 8: t = b'0' * (8 - len(payload)) payload = t + payload reply += num + payload self.net.send_packet("blastee-eth0", reply)
-
这部分要做的事情同样比较简单,需要根据到达的包构造一个ack回复包
- 首先需要设置ipv4包头以及以太网包头,并设置一个假的UDP包头
- 之后需要对包的内容进行填充,首先从收到的包提取出序号,然后提取可变长度负载的内容(最多提取8个字节,不足则补0),然后写入到新的包中
- 最后将包发送回去
Task 4: Blaster¶
-
这部分的设计相对来说比较复杂,是整个实验的重点
-
python self.net = net self.blasteeIp = blasteeIp self.num = int(num) self.length = int(length) self.senderWindow = int(senderWindow) self.timeout = int(timeout) self.recvTimeout = int(recvTimeout)
-
首先对传入的运行参数进行存储
-
python # 上次移动时间 self.lastMoveTime = time.time() # 包的序号 self.seq = 0 #窗口序列 self.window = [False for _ in range(self.num)] #LHS self.LHS = 0 #RHS self.RHS = 0 # 发送序列 self.sendSeq = queue.Queue() # 是否结束 self.end = False # 统计数据 self.beginTime = time.time() self.retx = 0 self.coarseTO = 0 self.totalBytes = 0 self.goodBytes = 0
-
存储维护窗口所需要的数据,以及最后统计结果所需要的量
- 使用window记录数据包的ACK返回状况
-
使用sendSeq记录等待发送的数据包队列(每次从中取出一个数据包进行发送)
-
python def handle_packet(self, recv: switchyard.llnetbase.ReceivedPacket): _, fromIface, packet = recv #获取seq num = packet[RawPacketContents].to_bytes()[:4] num = struct.unpack('>I', num)[0] self.window[num] = True print('receive: ',num) #更新LHS while self.LHS < self.num and self.window[self.LHS]: self.LHS += 1 #记录更新时间 self.lastMoveTime = time.time() print("refreah: ", self.lastMoveTime)
-
收到包的处理
-
首先解析包的序列号,window中对应的项设置为True标记位已经收到,然后检查左端点能否向右进行移动,如果可以移动,更新移动时间(方便后面判断是否出现了超时)
-
python def handle_no_packet(self): #计算统计结果 if self.LHS == self.num: if not self.end: self.end = True print('<--------------------------END------------------------------->') print('Total TX time: ', time.time() - self.beginTime, 's') print('Number of reTX: ', self.retx) print('Number of coarse TOs: ', self.coarseTO) print('Throughput (Bps): ', self.totalBytes / (time.time() - self.beginTime)) print('Goodput (Bps): ', self.goodBytes / (time.time() - self.beginTime)) print('<------------------------------------------------------------->') return print('[',self.LHS, ', ',self.RHS,']')
-
打印模块,如果发现窗口中所有的数据都已经被确认收到,则打印传输结果
-
python #检查是否超时 if time.time() - self.lastMoveTime > self.timeout/1000: print('out of time: ', time.time() - self.lastMoveTime) temp = [] self.coarseTO += 1 #添加没有收到的包 for i in range(self.LHS, min(self.RHS + 1, self.num)): if not self.window[i]: self.retx += 1 self.sendSeq.put(i) temp.append(i) print("retransmit: ", temp) self.lastMoveTime = time.time() #窗口滑动 elif self.RHS - self.LHS < self.senderWindow and self.seq < self.num: self.goodBytes += self.length self.sendSeq.put(self.seq) self.seq += 1 self.RHS += 1
-
首先判断是否出现了超时,如果出现了超时就遍历当前的窗口,将所有没有被确认的数据包放入带发送队列
-
如果没有出现超时,就检查能否进行窗口的滑动,如果可以滑动,则将新的包放入待发送队列
-
对于这两项的优先级逻辑有以下考量
-
如果出现了超时因该先处理当前的窗口中没有被确认的包,因为这很可能是发生了丢包,而不是继续向后进行发送,这样可以避免包被重复加入到待发送队列,并且使得包的发送更为有序
-
一次最多只将RHS向右移动一个位置,这是因为一次只能发送一个包,因此移动的更多没有意义,反而可能加重堆积的问题
-
-
python #从队列中取包进行发送 while not self.sendSeq.empty(): #获取包序号 num = self.sendSeq.get() #检查是否已经收到 if(self.window[num]): self.retx -= 1 continue self.totalBytes += self.length pkt = Ethernet() + IPv4() + UDP() pkt[0].src = "10:00:00:00:00:01" pkt[0].dst ="40:00:00:00:00:01" pkt[1].protocol = IPProtocol.UDP pkt[1].src = "192.168.100.1" pkt[1].dst = self.blasteeIp pkt[1].ttl = 64 pkt[2].src = 8080 pkt[2].dst = 8080 # 内容 pkt += struct.pack('>i', num) + struct.pack('>h', self.length) + b'0'*self.length print("send: ", num) self.net.send_packet("blaster-eth0", pkt) break
-
这部的思路比较简单,就是从队列中取出包,设置包的包头即内容并进行发送
- 这里有一处改动
- 当从待发送队列取出包时,先判断包是否已经收到ACK(对于一个被重传的包来说这很有可能),从而避免重复传输
- 测试发现这可以极大的减少带宽浪费,提高传输速率
Task 5: Running your code¶
- 手册数据测试
- 不难发现这里出现超时的频率\(31/100\)要高于设置的丢包率\(0.19\),经过打印输出分析可能是因为设置的超时时长\(300ms\)太短了,有些包虽然没有丢失,但并没有在时间内返回ACK,造成误判为丢包超时
超时长短测试¶
- 200ms
- 当超时时长过短时,大量的正常传输的包因为不能及时返回ACK而被认为发生丢包超时,因此会有大量的包被重复传输,浪费了带宽,从而导致传输时间变长,有效传输速率下降
-
500ms
- 经过测试发现,当超时时间设置为\(500ms\)或更大时,所有的数据包都能在超时到来之前返回ACK,这就保证了所有超时都是由丢包引起,此时\(12/100\)与丢包率相近
- 同时发现将超时时间设置为\(500ms\)会比\(300ms\)更优,具有更低的传输时间,因此在下面的测试中为了减少误判对测试结果的影响,均采用\(500ms\)为超时时间
窗口大小测试¶
- 1
- 当窗口很小时,会出现大量的闲置情况,即在一个\(100ms\)内不会有包被发送,造成带宽的浪费,传输时长显著增加
-
15
- 经过测试发现,实际上在窗口长度达到5后基本就不会出现线路闲置的状况,因此继续提升窗口长度的大小对传输速率的影响很小,此时的限制因素已经是线路的带宽了
-
50
- 当窗口长度继续增长时,传输效率反而会有所下降,这是因为窗口长度增加了但是线路的传输速率没有发生变化,发生重传的概率有所上升,出现更多的重复传输浪费带宽
丢包率测试¶
- 在超时时长足够长的情况下(500ms),超时次数/包的数目~~丢包率
- 丢包率0%
- 此时不会出现超时重传
- 但是如果超时时长更短(300ms),可能由于不能再=在限时内返还ACK,因此可能会出现误判造成的超时
-
丢包率19%
- 丢包率39%
抓包测试¶
- 首先进行没有丢包的测试
- 可以看到发送方和接收方都收发了共200个包(即没有出现任何丢包及重传)
- 数据包的结构如图
- 框出的分别为包的序列号
0x63
(99),4字节 - 四字节的内容长度,
0x64
(100),2字节 - 以及包的内容填充,100字节
- 框出的分别为包的序列号
-
ACK回复包结构如图
- 分别为回复的确认号
0x5f
,4字节 - 以及8字节的有效长度负载
- 分别为回复的确认号
-
然后使用手册的参数进行更为复杂的有丢包和超时的测试
- 结合xterm打印输出分析开头部分的情况
- 首先blaster发送数据包0,1(这个0恰好被丢弃了)
- 然后出现了超时,进行重传,重新发送0
- 收到了来自blastee的ACK1
- 由于1已经被收到了,因此不再对其进行重传,blaster继续发送2
- 收到了ACK0,窗口左边界移动到2
- 继续发送3,4
- 收到了2、3,左边界移动
- .....
实验总结¶
- 这次实验的难度相对较低,任务量也较少。
- 实验手册的内容和细节比较多,一方面需要认真阅读实验手册的要点,另一方面对于实验手册中没有提到的关于协议细节的设计需要结合课上的知识思考合理的方案。
- 经过这次实验,对可靠传输速率的影响因素有了更加深刻的影响,体会到了网络中的各种意外情况对传输以及协议设计等影响,更加理解协议设计的原因