实验四 增强型可靠文件传输系统
一、实验目的和任务
- 掌握基于队列的多文件传输机制
- 理解断点续传的实现原理
- 学习文件传输完整性保障方法
二、实验内容
基础功能验证
- 单文件传输功能测试
- 服务器状态监控测试
- 传输日志记录验证
新增功能实现
- 多文件队列传输功能
- 断点续传支持
三、实验步骤
4.1 客户端功能扩展
参考代码:
关键代码修改点:
文件队列管理:
# 新增队列和状态变量
self.file_queue = queue.Queue()
self.is_transferring = False
# 修改后的文件选择方法
def select_file(self):filepaths = filedialog.askopenfilenames()if filepaths:for filepath in filepaths:self.file_queue.put(filepath)self.log.insert(END, f"已添加: {os.path.basename(filepath)}\n")if not self.is_transferring:threading.Thread(target=self.process_queue).start()
新增队列处理线程:
def process_queue(self):self.is_transferring = Truewhile not self.file_queue.empty():filepath = self.file_queue.get()self.upload_file(filepath)
self.is_transferring = False
断点续传功能:
上传文件方法修改def upload_file(self, filepath):try:client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect(('localhost', 12345))# 发送文件元数据filename = os.path.basename(filepath)filesize = os.path.getsize(filepath)client.send(f"{filename} |{filesize}".encode())# 等待服务器确认ack = client.recv(1024).decode()if ack.startswith('RESUME'):recieved = int(ack.split('|')[1])mode = 'rb+' #从断点处继续读else:recieved = 0mode = 'rb'# 分块传输文件with open(filepath, mode) as f:f.seek(recieved)while recieved <filesize:chunk = f.read(1024)if chunk:client.send(chunk)recieved += len(chunk)self.log.insert(END, f"{filename} 传输成功\n")except Exception as e:self.log.insert(END, f"错误: {e}\n")finally:client.close()
完整python脚本如下:
import socket
import os
import threading
import queue
from tkinter import *
from tkinter import filedialog
from tkinter import messagebox
class FileTransferClient:def __init__(self, root):self.root = rootself.root.title("文件传输客户端")# 文件队列和状态self.file_queue = queue.Queue()self.is_transferring = False# 创建界面组件self.create_widgets()def create_widgets(self):# 文件选择按钮self.select_btn = Button(self.root, text="选择文件", command=self.select_file)self.select_btn.pack(pady=10)# 传输日志self.log = Text(self.root, height=15, width=60)self.log.pack(pady=10)def select_file(self):filepaths = filedialog.askopenfilenames()if filepaths:for filepath in filepaths:self.file_queue.put(filepath)self.log.insert(END, f"已添加: {os.path.basename(filepath)}\n")if not self.is_transferring:threading.Thread(target=self.process_queue).start()def process_queue(self):self.is_transferring = Truewhile not self.file_queue.empty():filepath = self.file_queue.get()self.upload_file(filepath)self.is_transferring = Falsedef upload_file(self, filepath):try:client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect(('localhost', 12345))# 发送文件元数据filename = os.path.basename(filepath)filesize = os.path.getsize(filepath)client.send(f"{filename}|{filesize}".encode())# 等待服务器确认ack = client.recv(1024).decode()if ack.startswith('RESUME'):received = int(ack.split('|')[1])mode = 'rb+'else:received = 0mode = 'rb'# 分块传输文件with open(filepath, mode) as f:f.seek(received)while received < filesize:data = f.read(1024)if not data:breakclient.send(data)received += len(data)self.log.insert(END, f"{filename} 传输成功\n")except Exception as e:self.log.insert(END, f"错误: {e}\n")finally:client.close()
if __name__ == "__main__":root = Tk()app = FileTransferClient(root)root.mainloop()
4.2 服务器功能扩展
参考代码:
关键代码修改点:
断点续传支持:
def handle_client(self, client_socket):# 检查文件是否存在if os.path.exists(filename):received = os.path.getsize(filename)client_socket.send(f"RESUME|{received}".encode())mode = 'ab' # 追加模式else:client_socket.send(b"ACK")mode = 'wb'received = 0
完整python脚本如下:
import socket
import os
import threading
from datetime import datetime
class FileTransferServer:def __init__(self, host='localhost', port=12345):self.host = hostself.port = portself.server_socket = Noneself.running = Falsedef start(self):self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.server_socket.bind((self.host, self.port))self.server_socket.listen(5)self.running = Trueprint(f"服务器启动,监听 {self.host}:{self.port}")while self.running:try:client_socket, addr = self.server_socket.accept()print(f"新连接来自: {addr}")# 为每个客户端创建新线程client_thread = threading.Thread(target=self.handle_client,args=(client_socket,))client_thread.start()except Exception as e:print(f"服务器错误: {e}")breakdef stop(self):self.running = Falseif self.server_socket:self.server_socket.close()print("服务器已停止")def handle_client(self, client_socket):try:# 接收文件元数据metadata = client_socket.recv(1024).decode()filename, filesize = metadata.split('|')filesize = int(filesize)# 检查文件是否存在if os.path.exists(filename):received = os.path.getsize(filename)client_socket.send(f"RESUME|{received}".encode())mode = 'ab' # 追加模式else:client_socket.send(b"ACK")mode = 'wb'received = 0# 接收文件数据with open(filename, mode) as f:while received < filesize:data = client_socket.recv(1024)if not data:breakf.write(data)received += len(data)print(f"文件 {filename} 接收完成 ({received}/{filesize} bytes)")except Exception as e:print(f"处理客户端时出错: {e}")finally:client_socket.close()
if __name__ == "__main__":server = FileTransferServer()try:server.start()except KeyboardInterrupt:server.stop()
4.3 实验验证步骤
- 基础传输测试:
- 启动Server.py
- 运行Client.py
- 选择单个文件传输
- 观察服务器接收情况
- 多文件队列测试:
- 启动Server2.py
- 运行Client2.py
- 同时选择多个文件(建议3-5个)
- 观察队列传输顺序和日志记录
- 断点续传测试:
- 传输大文件(>50MB)
- 在传输过程中强制关闭客户端
- 重新启动传输同一文件
- 验证文件完整性(通过文件大小比对)
四、测试结果
五、思考题
1.MD5校验是否能完全保证文件正确性?为什么?
MD5校验不能完全保证文件的正确性。虽然MD5可以用来检测文件完整性,因为它通过生成一个固定长度的哈希值来唯一标识文件内容,但如果两个不同的文件生成了相同的MD5哈希值(这种情况称为哈希碰撞),那么MD5校验就无法区分这两个文件了。此外,MD5已经被证明不够安全,存在被恶意攻击者故意构造碰撞的情况。因此,对于需要更高安全性的场景,通常推荐使用更安全的哈希算法,如SHA-256。
2.进度条更新为什么要用after()方法?
在GUI编程中,after()
方法常用于非阻塞地执行某些任务,比如定时更新进度条。这是因为after()
方法可以在指定的时间间隔后执行一个函数或方法调用,而不需要阻塞主线程。这样可以保持用户界面的响应性,防止因为长时间的计算或网络操作导致界面卡顿。
3.如何实现服务端的多客户端并发处理?
实现服务端的多客户端并发处理通常可以采用多线程或多进程的方式。例如,在Python中可以使用socket
库结合threading
或multiprocessing
库来为每个客户端创建一个独立的线程或进程,从而实现并发。另一种方法是使用异步编程,如asyncio
库,通过事件循环管理多个客户端的请求,这种方式在处理大量并发连接时效率更高。
4.传输过程中突然关闭窗口会导致什么问题?如何解决?
传输过程中突然关闭窗口可能会导致数据传输不完整,文件损坏,或者服务端和客户端之间的连接异常中断。为了解决这个问题,通常可以采用以下措施:
-
实现数据包的确认机制,确保每个数据包都被正确接收。
-
使用断点续传功能,使得在传输中断后可以从上次中断的位置继续传输。
-
设计良好的错误处理机制,能够在检测到连接中断时尝试恢复连接或通知用户重新传输文件。