GBN(Go-Back-N)协议的python本机模拟实现-计算机网络

熟悉GBN协议的发送方、接收方状态转换过程、窗口滑动、超时重传、包(packet)装配以及解析、对udt(基于UDP的不可靠传输)的模拟,了解全双工通信的要点。

利用python实现GBN协议。

模拟本次实验需要两个终端,一个承载sender.py,另一个承载receiver.py,在实验时需要先执行sender.py,之后执行receiver.py,两端的ip地址和端口号需要一致。

  1. 设置发送分组及定时功能

GBN协议需要将发送数据构建成若干个binary packet来模拟过程并传输,本实验中一个packet的结构为(代码详见附件lib\packet.py)

编写make_pkt函数,其调用了int.to_bytes(处理acknumber)和encode(处理data)来转换成二进制

getacknumber用于获取前四位的acknumber/expectednumber,extract用于将后面的data解码。

  1. EXPECTEDSEQSUM_SIZE = 4  
  2.   
  3. def make_pkt(expectedseqnum, data):  
  4.     b_expectedseqsum = expectedseqnum.to_bytes(EXPECTEDSEQSUM_SIZE, byteorder = 'little', signed = True)  
  5.     b_data = data.encode(encoding = 'utf-8')  
  6.     packet = b_expectedseqsum + b_data  
  7.     return packet  
  8.   
  9. def getacknum(rcvpkt):  
  10.     return int.from_bytes(rcvpkt[0:EXPECTEDSEQSUM_SIZE], byteorder = 'little', signed = True)  
  11.   
  12. def extract(rcvpkt):  
  13.     return rcvpkt[EXPECTEDSEQSUM_SIZE:].decode(encoding = 'utf-8')  
  14.   
  15. def hasseqnum(rcvpkt, expectedseqnum):  
  16.     if int.from_bytes(rcvpkt[0:EXPECTEDSEQSUM_SIZE], byteorder = 'little', signed = True) == expectedseqnum:  
  17.         return True  
  18.     else:  
  19.         return False  

定时功能实现:代码详见lib\timer.py

Python的timer包中设计有起始时间点start_point,状态status默认为0,以及用户传入的超时时间间隔timeinterval。

start_timer用于开始计时,如果计时器的状态为1,则会刷新起始点并重新开始计时。

stop_timer用户停止计时,设计计时器状态为0,并打印输出duration。

timeout会返回一个bool用于判断目前是否已经超时,如果计时器状态为1,并且duration已经超过了设定的time interval,则会返回一个True,表示计时器已经超时,其他情况下返回False。

  1. import time  
  2.   
  3. class Timer():  
  4.     status = 0  
  5.     def __init__(self,timeinterval):  
  6.         self.timeinterval = timeinterval  
  7.       
  8.     def start_time(self):  
  9.         if self.status:  
  10.             print("[timer]: restart timer")  
  11.         self.status = 1  
  12.         self.start_point = time.time()  
  13.       
  14.     def stop_time(self):  
  15.         self.status = 0  
  16.         print("[timer]: timer stop")  
  17.         self.duration = time.time() - self.start_point  
  18.         print("[timer]: duration is ",self.duration)  
  19.   
  20.   
  21.     def timeout(self):  
  22.         if self.status:  
  23.             if time.time() - self.start_point > self.timeinterval:  
  24.                 print("[timer]: time out")  
  25.                 return True  
  26.             else:  
  27.                 return False  
  28.         else:  
  29.             return False  

模拟udt传输

由于需要全双工通信,并且实在本机上演示发送方和接收方的数据传输,因此正常情况下并不会发生丢包现象,但是为了模拟该情况,利用random.random()来指定阈值产生随机丢包事件,如果丢包事件发生,则本次packet的传输不会完成。

  1. import random  
  2. class Udt():  
  3.     def __init__(self, socket, miss_prob):  
  4.         self.socket = socket  
  5.         self.miss_prob = miss_prob  
  6.   
  7.     def udt_send(self, snd_pkt):  
  8.         R = random.random()  
  9.         if R < self.miss_prob:  
  10.             print("[udt_send]: appear to miss the packet")  
  11.         else:  
  12.             print("[udt_send]: send the packet successfully")  
  13.             self.socket.send(snd_pkt)  
  1. 发送方搭建(代码详见sender.py

利用socket库创建并绑定TCP的socket套接字,利用上层模拟函数upper_level,调用rdt_send将packet发送至receiver,完成send.txt读入后,使用thread库中的start_new_thread来创建新线程分别处理{rdt_rcv,timeout}事件

函数rdt_send模拟sender发送部分,如果传输未完成并且nextseqnum不超过base+N,将nextseqnum的packet使用udt_send发送至接收方,如果超过了,refuse_data。

函数rdt_rcv模拟sender接受部分,rdt_rcv通过套接字接收响应包,并利用getacknum获取应答码,设置base为acknum+1,判断base和nextseqnum的关系,判断计时器是否重启或停止。

函数timeout模拟超时时间,超时发生后,重启计时器,重传base到nextseqnum-1的所有packet

  1. import socket  
  2. import lib.packet as packet  
  3. import _thread  
  4. import lib.timer as timer  
  5. import time  
  6. import os  
  7. import lib.udt as udt  
  8.   
  9.   
  10. DATA_SIZE = 16  
  11. # data size per packet  
  12.   
  13. oripkt = []  
  14. # restore original packets  
  15.   
  16. mutex = _thread.allocate_lock()  
  17. # multi-thread process  
  18.   
  19. index = 0  
  20. # record how many packets to be sent  
  21.   
  22. def upper_invoke():  
  23.     global index  
  24.   
  25.     with open(os.path.join('file','send.txt'),'rb') as f:  
  26.         data = f.read()  
  27.     # load the file  
  28.     print("[upper-level]: finish reading data.")  
  29.   
  30.     while True:  
  31.         if index * DATA_SIZE > len(data):  
  32.             break  
  33.         oripkt.append((data[index * DATA_SIZE: (index + 1) * DATA_SIZE]).decode(encoding = 'utf-8'))  
  34.         index = index + 1  
  35.     print("[upper-level]: finish reloading data.")  
  36.     # the binary data is divided into original packets(oripkt) in DATA_SIZE bytes   
  37.   
  38.     conn.send(packet.make_pkt(index,'index'))  
  39.     time.sleep(1)  
  40.   
  41.     _thread.start_new_thread(rdt_rcv,())  
  42.     _thread.start_new_thread(rdt_timeout,())  
  43.   
  44.     for i in range(index):  
  45.         if rdt_send(oripkt[i]):  
  46.             print("[upper-level]: send data successfully.")  
  47.         else:  
  48.             while True:  
  49.                 print("[upper-level]: cannot send data now.")  
  50.                 if rdt_send(oripkt[i]):  
  51.                     print("[upper-level]: send data successfully.")  
  52.                     break  
  53.     # invoke rdt_send  
  54.   
  55.     print("[upper-level]: finish sending")  
  56.     f.close()  
  57.   
  58. """ 
  59. SENDER 
  60. """  
  61. SENDER_ADDR = ('localhost',9500)  
  62. # sender's address.  
  63.   
  64. sndpkt = []  
  65. # restore sndpkts(send packets).  
  66.   
  67. base = 0  
  68. nextseqnum = 0  
  69. N = 5  
  70.   
  71. timer = timer.Timer(5)  
  72.   
  73. is_close = 0  
  74.   
  75. def rdt_send(data):  
  76.     global base  
  77.     global nextseqnum  
  78.     global N  
  79.   
  80.     sender = udt.Udt(conn, 0.001)  
  81.     if nextseqnum < base + N:  
  82.         mutex.acquire() 
  83.         sndpkt.append(packet.make_pkt(nextseqnum, data))  
  84.         sender.udt_send(sndpkt[nextseqnum])  
  85.   
  86.         if (base == nextseqnum):  
  87.             timer.start_time()  
  88.         nextseqnum += 1  
  89.         mutex.release()  
  90.         print("[sender]: ", nextseqnum, "sndpkt is sent to receiver successfully")  
  91.         time.sleep(1)  
  92.         return True  
  93.     else:  
  94.         print("[sender]: refuse data, because there is no available window size")  
  95.         time.sleep(1)  
  96.         return False  
  97.   
  98. def rdt_timeout():  
  99.     global base  
  100.     global nextseqnum  
  101.     global timer  
  102.   
  103.     sender = udt.Udt(conn, 0.001)  
  104.     # define the udt_send which may loss packet  
  105.   
  106.     while True:  
  107.         if timer.timeout():  
  108.             mutex.acquire()  
  109.             print("[sender]: time out")  
  110.             timer.start_time()  
  111.             for i in range(base,nextseqnum):  
  112.                 # stable transmit  
  113.                 # conn.send(sndpkt[i])  
  114.                 sender.udt_send(sndpkt[i])  
  115.             print("[sender]: re-send from", base, "to", nextseqnum)  
  116.             mutex.release()  
  117.   
  118.   
  119. def rdt_rcv():  
  120.     global base  
  121.     global nextseqnum  
  122.     global timer  
  123.     global index  
  124.   
  125.     global is_close  
  126.   
  127.     while True:  
  128.         print("[sender]: wait to receive from receiver")  
  129.         rcvpkt = conn.recv(1024)  
  130.         if packet.extract(rcvpkt) == 'finish':  
  131.             print("[sender]: sender has finish the transmit mission, close the connection")  
  132.             conn.close()  
  133.             is_close = 1  
  134.             break  
  135.   
  136.         print("[sender]: receive packet")  
  137.         mutex.acquire()  
  138.         base = packet.getacknum(rcvpkt) + 1  
  139.         mutex.release()  
  140.         print("", base)  
  141.         # update base  
  142.         if base == nextseqnum:  
  143.             timer.stop_time()  
  144.         else:  
  145.             timer.start_time()  
  146.   
  147. if __name__ == '__main__':  
  148.     socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  149.     # UDP connection  
  150.     socket.bind(SENDER_ADDR)  
  151.     # create socket  
  152.     socket.listen(1)  
  153.     print("[sender]: waiting to connect")  
  154.   
  155.     conn, addr = socket.accept()  
  156.     print("[sender]: connect to :", addr)  
  157.   
  158.     upper_invoke()  
  159.     while not is_close:  
  160.         print("[system]: wait the receiver to receive all the packets")  
  161.         time.sleep(1)  

  1. 接收方搭建(代码详见附件receiver.py

函数receive接收来自发送方的packet,判断包中序号是否和expectednum相匹配。如果相匹配,则会make_pkt,并响应“收到了预期序号的包”,并将预期序号自增1;如果不匹配,则会响应“收到了不相匹配的包”,发送上次所制作的包。

如果传输完所有的包,关闭连接。

  1. import socket  
  2. import os  
  3. import lib.packet as packet  
  4. import lib.udt as udt  
  5. import time  
  6.   
  7.   
  8. RECEIVER_ADDR = ('localhost',9500)  
  9. # receiver's address  
  10. expected_num = 0  
  11. data_cache = ""  
  12.   
  13. def receive():  
  14.   
  15.     global expected_num  
  16.     global data_cache  
  17.   
  18.     sender =  udt.Udt(socket, 0.001)  
  19.     # create unstable connection  
  20.   
  21.     snd_pkt = packet.make_pkt(expected_num - 1, 'ACK')  
  22.   
  23.     rcv_pkt = socket.recv(1024)  
  24.     # receive index  
  25.     if packet.extract(rcv_pkt) == 'index':  
  26.         index = packet.getacknum(rcv_pkt)  
  27.         print("[receiver]: receiver get index", index)  
  28.   
  29.     print("[receiver]: finish initalizing")  
  30.     # default value  
  31.     while True:  
  32.         rcv_pkt = socket.recv(1024)  
  33.         if packet.hasseqnum(rcv_pkt, expected_num):  
  34.             # match the expected number  
  35.             data = packet.extract(rcv_pkt)  
  36.             print("[receiver]: receive data:", data)  
  37.             data_cache += data  
  38.             # deliver data  
  39.             snd_pkt = packet.make_pkt(expected_num, 'ACK')  
  40.   
  41.             sender.udt_send(snd_pkt)  
  42.             print("[receiver]: successfully match the expected number and return an ACK", expected_num, "to sender")  
  43.             expected_num += 1  
  44.         else:  
  45.             socket.send(snd_pkt)  
  46.             print("[receiver]: fail to match the expected number and return an earliest ACK", expected_num, "to sender")  
  47.         time.sleep(1)  
  48.         if index == expected_num:  
  49.             print("[receiver]: receiver has received all the packets from the sender")  
  50.   
  51.             end_pkt = packet.make_pkt(0,'finish')  
  52.             socket.send(end_pkt)  
  53.             # end the connection  
  54.               
  55.             with open(os.path.join("file","receive.txt"),'w') as f:  
  56.                 f.write(data_cache)  
  57.             break  
  58.   
  59. if __name__ == '__main__':  
  60.     socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  61.     # UDP connection  
  62.     socket.connect(RECEIVER_ADDR)  
  63.     receive()  
  64.     socket.close() 

开启两个终端,先执行sender.py,再执行receiver.py,传输过程如下:

发送方发送的内容和接收方接收到内容如下:

实验结果分析:

      本次实验能够正确传输结果。

      但是传输时会出现问题:如果将udt传输的丢包率调大,例如调至0.1左右时,传输结果有时会出现若干个错误的连接符号,有时也会出现不正常的换行

由于此实验内容不包含校验和checksum,因此数据的正确性难以保证;就算checksum被加入,面对丢包率过高的网络依然难以保证报文的正确传输,不能完全保证所传输的内容正确。

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值