# realiableUDP **Repository Path**: TangSNeng/realiable-udp ## Basic Information - **Project Name**: realiableUDP - **Description**: 开源软件设计的大作业 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-22 - **Last Updated**: 2023-12-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # **基于UDP实现可靠大文件传输** **引言** 随着互联网技术的迅猛发展和广泛应用,人们对于大文件传输的需求逐渐增多。在许多场景下,如高清视频、大型软件、游戏、3D模型等,大文件的传输需求显得尤为重要。 UDP(用户数据报协议)是一种无连接的传输层协议,它不需要建立连接就可以直接发送数据。与TCP相比,UDP更加简单、快速,适用于对实时性要求较高的应用场景,如在线视频、语音通话等。在大文件传输中,UDP协议可以通过将大文件切分成小数据包,并逐个发送的方式,有效提高了传输效率和速度。 本软件正是基于UDP协议的特点,实现高效、可靠的大文件传输功能。通过合理的文件切割和重组算法,以及可靠的数据传输机制,本软件可以快速、准确地完成大文件的传输任务。同时,本软件还提供了友好的用户界面,方便用户进行文件选择、传输控制等操作。无论是个人用户还是企业用户,本软件都可以满足其大文件传输的需求,提供稳定、高效的文件传输服务。 **软件概述** 实现大文件传输: - 将大文件切分为大小适当(1024个字节)的数据块,以适应UDP协议传输的特性。 - 确定每个数据包的结构,包括序列号、ACK、头部大小、包的大小、结束位、数据等信息。 - 多线程读取文件来加快文件的读取。 - 多线程发送来加快文件的发送。 - 接收端无需重组数据包,只需根据缓冲区与文件的映射直接写入即可。 - 设置发送和接收缓冲区,无需等待文件的IO。 实现可靠传输: - 超时重传,当发送完一个数据包后,若3秒内没有收到反馈包,则重传该数据包 - 请求重传,当接收端发送的ack和seq无法对应,则重传该数据包 - 3次握手与4次挥手。 本软件设计的数据包结构如下: ![](imgs/基于UDP实现可靠大文件传输(1).001.png) 其中ACK和SEQ分别占32位,包的大小(packet\_size)占8位,头部大小(head\_size)占4位,结束位标志(end)占一位,data占1024个字节。总的数据包大小1036个字节,默认大端对齐。 **实现细节** 1. 客户端 首先创建一个UDP套接字,`socket.socket(socket.AF_INET,socket.SOCK_DGRAM)`创建了一个UDP套接字,其中`AF_INET`表示使用`IPv4`网络。`SOCK\_DGRAM`表示这是一个UDP套接字。随后,设置套接字的发送和接收缓冲区大小为1038\*64字节,即64个数据包大小。 ``` client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) client_socket.setsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF,SEND_BUF_SIZE) client_socket.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,RECV_BUF_SIZE) ``` 随后模拟TCP的3次握手来建立客户端与服务器之间的连接,并发送要传输的文件名,同时打印相关输出日志。 ``` def connect(client_socket): get_connect_data = "Sender require connection" client_socket.sendto(get_connect_data.encode('utf-8'),SERVER_ADDRESS) connect_recv_data, server_address = client_socket.recvfrom(BUF_SIZE) print(connect_recv_data.decode('utf-8')+' from '+str(server_address[0])+":"+str(server_address[1])) confirm_connect_data = "Sender confirm connection"+FILE_NAME.split('/')[-1] client_socket.sendto(confirm_connect_data.encode('utf-8'),server_address) ``` 建立连接之后,开启多线程来读取文件,这里为4个线程来提高了文件传输的效率。`thread.join()`会阻塞当前线程,直到thread线程执行完毕。这样可以确保所有线程都执行完毕后,再继续执行主线程的后续代码。 ``` with open(FILE_NAME, 'rb') as file: for i in range(4): thread = threading.Thread(target=get_packet, args=(timestamp, file, client_socket)) thread.start() threads.append(thread) for thread in threads: thread.join() ``` 详细的读取文件获取数据包的代码如下。首先开启一个无限循环,直到文件读取完毕,由于多个线程可能同时访问和修改全局变量,所以需要使用锁来同步线程。每个线程在得到seq后,直接将文件指针移到seq\*1024的位置,这样就能实现快速多线程读取,读取之后将相应的数据以及包信息添加到数据包中,然后发送数据,并且计算已发送的字节数和发送时间来计算发送速度。 ``` def get_packet(timestamp, file, client_socket): global seq,ack,speed,sended_bytes while True: lock.acquire() file.seek(seq*1024) data = file.read(1024) if not data: print('共发送'+str(seq)+'个数据包,文件总大小为'+str(os.stat(FILE_NAME).st_size)) lock.release() break packet_size = 4 + 4 + 1 + 1 + 1 + 1 + 1024 end_flag = 0 if len(data) == 1024 else 1 header_size = 12 packet = packet_struct.pack(ack, seq, packet_size, header_size, end_flag, data) seq += 1 ack += 1 lock.release() send_packet(client_socket, packet,seq) sended_bytes = len(data)*(seq+1) speed = (sended_bytes/(time.time()-timestamp))/1000 ``` 发送和重传的代码如下。在每次发送后,开启一个线程来执行超时重传的操作,这样,主线程就能执行其他的操作。在发送之后,若5秒内没有收到反馈包,则再次发送。同时,若收到的反馈包ack和seq对应不上,也执行重传操作。 ``` def send_packet(client_socket, packet, seq): client_socket.sendto(packet,SERVER_ADDRESS) stop_event = threading.Event() resend_thread = threading.Thread(target=resend_packet, args=(client_socket,packet,seq,stop_event)) resend_thread.start() timer = threading.Timer(5, stop_event.set) timer.start() def resend_packet(client_socket, packet,seq,stop_event): while not stop_event.is_set(): feedback_pack, _ = client_socket.recvfrom(BUF_SIZE) ack = feedback_struct.unpack(feedback_pack)[0] if ack != seq: break else: continue ``` 最后,执行TCP的4次挥手。 ``` data = 'Client requests disconnection' print(data) client_socket.sendto(data.encode('utf-8'),SERVER_ADDRESS) data,client_addr = client_socket.recvfrom(BUF_SIZE) print(data.decode('utf-8')) data,client_addr = client_socket.recvfrom(BUF_SIZE) print(data.decode('utf-8')) data = 'Client allows disconnection' client_socket.sendto(data.encode('utf-8'),SERVER_ADDRESS) print(data) print('The connection between client and server has been interrupted') client_socket.close() ``` **服务端** 首先创建一个UDP套接字,`socket.socket(socket.AF_INET,socket.SOCK_DGRAM)`创建了一个UDP套接字,其中`AF\_INET`表示使用`IPv4`网络。`SOCK\_DGRAM`表示这是一个UDP套接字。随后,设置套接字的发送和接收缓冲区大小为1038\*64字节,即64个数据包大小。 ![](imgs/基于UDP实现可靠大文件传输(1).002.png) 随后模拟TCP的3次握手来建立客户端与服务器之间的连接,并接收要传输的文件名,同时打印相关输出日志。 ![](imgs/基于UDP实现可靠大文件传输(1).003.png) 连接建立完成后,`data, client_addr = s.recvfrom(RECV_BUF_SIZE)`:从套接字 s 接收数据,使用指定的缓冲区大小 `RECV_BUF_SIZE`。接收到的数据被解包为 data,而 `client_addr` 表示客户端的地址。`unpacked_data = packet_struct.unpack(data)`:使用由 `packet_struct `指定的结构解包接收到的数据。解包后的数据似乎包含有关数据包的信息,如 ACK 号码、序列号、数据包大小、头部大小、结束标志和文件数据。`file.seek(seq_received * 1024)`:根据接收到的序列号,定位文件中的位置,假设块大小为 1024 字节。`file.write(file_data)`:将接收到的文件数据写入文件的指定位置。若检测到end=1跳出循环。 ![](imgs/基于UDP实现可靠大文件传输(1).004.png) 传输完成后,四次挥手断开连接 ![](imgs/基于UDP实现可靠大文件传输(1).005.png) **代码演示情况** 首先先运行执行python server.py命令来运行服务器代码。 ![](imgs/基于UDP实现可靠大文件传输(1).006.png) 随后,执行python UI.py来执行客户端代码。 ![](imgs/基于UDP实现可靠大文件传输(1).007.png) 选择相应的IP地址和文件,然后点击Start Transfer按钮即可。 **性能指标** 本地模拟传输速度: ![](imgs/基于UDP实现可靠大文件传输(1).008.png) Client端内存占用 ![](imgs/基于UDP实现可靠大文件传输(1).009.png) server端内存占用 ![image-20231228170236598](imgs/image-20231228170236598-17037541817991-17037541833843.png) **总结** 本软件基于UDP协议实现了高效、可靠的大文件传输功能。通过合理的文件切割和重组算法,以及可靠的数据传输机制,本软件可以快速、准确地完成大文件的传输任务。同时,本软件还提供了友好的用户界面,方便用户进行文件选择、传输控制等操作。无论是个人用户还是企业用户,本软件都可以满足其大文件传输的需求,提供稳定、高效的文件传输服务。 在实现过程中,本软件采用了以下关键技术: - 将大文件切分为大小适当(1024个字节)的数据块,以适应UDP协议传输的特性。 - 确定每个数据包的结构,包括序列号、ACK、头部大小、包的大小、结束位、数据等信息。 - 多线程读取文件来加快文件的读取。 - 多线程发送来加快文件的发送。 - 接收端无需重组数据包,只需根据缓冲区与文件的映射直接写入即可。 - 设置发送和接收缓冲区,无需等待文件的IO。 此外,本软件还实现了可靠传输机制,包括超时重传、请求重传、3次握手与4次挥手,确保了数据传输的可靠性。 在实际应用中,本软件具有较高的传输速度和效率,能够满足各种大文件传输场景的需求。同时,本软件还具有较好的扩展性和兼容性,可以方便地应用于各种网络环境和设备。