实验四 增强型可靠文件传输系统

一、实验目的和任务

  1. 掌握基于队列的多文件传输机制
  2. 理解断点续传的实现原理
  3. 学习文件传输完整性保障方法

二、实验内容

基础功能验证

  1. 单文件传输功能测试
  2. 服务器状态监控测试
  3. 传输日志记录验证

新增功能实现

  1. 多文件队列传输功能
  2. 断点续传支持

三、实验步骤

4.1 客户端功能扩展

参考代码:

关键代码修改点:

python 复制代码
文件队列管理:
# 新增队列和状态变量
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 = True
    while 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 = 0
                mode = '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脚本如下:

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 = root
        self.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 = True
        while 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'):
                received = int(ack.split('|')[1])
                mode = 'rb+'
            else:
                received = 0
                mode = 'rb'
            
            # 分块传输文件
            with open(filepath, mode) as f:
                f.seek(received)
                while received < filesize:
                    data = f.read(1024)
                    if not data:
                        break
                    client.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 服务器功能扩展

参考代码:

关键代码修改点:

python 复制代码
断点续传支持:
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脚本如下:

python 复制代码
import socket
import os
import threading
from datetime import datetime
class FileTransferServer:
    def __init__(self, host='localhost', port=12345):
        self.host = host
        self.port = port
        self.server_socket = None
        self.running = False
        
    def 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 = True
        
        print(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}")
                break
    
    def stop(self):
        self.running = False
        if 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:
                        break
                    f.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 实验验证步骤

  1. 基础传输测试:
  1. 启动Server.py
  2. 运行Client.py
  3. 选择单个文件传输
  4. 观察服务器接收情况
  1. 多文件队列测试:
  1. 启动Server2.py
  2. 运行Client2.py
  3. 同时选择多个文件(建议3-5个)
  4. 观察队列传输顺序和日志记录
  1. 断点续传测试:
  1. 传输大文件(>50MB)
  2. 在传输过程中强制关闭客户端
  3. 重新启动传输同一文件
  4. 验证文件完整性(通过文件大小比对)

四、测试结果

五、思考题

1.MD5校验是否能完全保证文件正确性?为什么?

MD5校验不能完全保证文件的正确性。虽然MD5可以用来检测文件完整性,因为它通过生成一个固定长度的哈希值来唯一标识文件内容,但如果两个不同的文件生成了相同的MD5哈希值(这种情况称为哈希碰撞),那么MD5校验就无法区分这两个文件了。此外,MD5已经被证明不够安全,存在被恶意攻击者故意构造碰撞的情况。因此,对于需要更高安全性的场景,通常推荐使用更安全的哈希算法,如SHA-256。

2.进度条更新为什么要用after()方法?

在GUI编程中,after()方法常用于非阻塞地执行某些任务,比如定时更新进度条。这是因为after()方法可以在指定的时间间隔后执行一个函数或方法调用,而不需要阻塞主线程。这样可以保持用户界面的响应性,防止因为长时间的计算或网络操作导致界面卡顿。

3.如何实现服务端的多客户端并发处理?

实现服务端的多客户端并发处理通常可以采用多线程或多进程的方式。例如,在Python中可以使用socket库结合threadingmultiprocessing库来为每个客户端创建一个独立的线程或进程,从而实现并发。另一种方法是使用异步编程,如asyncio库,通过事件循环管理多个客户端的请求,这种方式在处理大量并发连接时效率更高。

4.传输过程中突然关闭窗口会导致什么问题?如何解决?

传输过程中突然关闭窗口可能会导致数据传输不完整,文件损坏,或者服务端和客户端之间的连接异常中断。为了解决这个问题,通常可以采用以下措施:

  • 实现数据包的确认机制,确保每个数据包都被正确接收。

  • 使用断点续传功能,使得在传输中断后可以从上次中断的位置继续传输。

  • 设计良好的错误处理机制,能够在检测到连接中断时尝试恢复连接或通知用户重新传输文件。

相关推荐
Jay_2725 分钟前
python项目如何创建docker环境
开发语言·python·docker
老胖闲聊44 分钟前
Python Django完整教程与代码示例
数据库·python·django
爬虫程序猿1 小时前
利用 Python 爬虫获取淘宝商品详情
开发语言·爬虫·python
noravinsc1 小时前
django paramiko 跳转登录
后端·python·django
声声codeGrandMaster1 小时前
Django之表格上传
后端·python·django
元直数字电路验证1 小时前
Python数据分析及可视化中常用的6个库及函数(一)
python·numpy
waterHBO1 小时前
一个小小的 flask app, 几个小工具,拼凑一下
javascript·vscode·python·flask·web app·agent mode·vibe coding
智商不够_熬夜来凑1 小时前
anaconda安装playwright
开发语言·python
溜溜刘@♞1 小时前
python变量
python
丁值心2 小时前
6.01打卡
开发语言·人工智能·python·深度学习·机器学习