一、實驗目的和任務
- 掌握基于隊列的多文件傳輸機制
- 理解斷點續傳的實現原理
- 學習文件傳輸完整性保障方法
二、實驗內容
基礎功能驗證
- 單文件傳輸功能測試
- 服務器狀態監控測試
- 傳輸日志記錄驗證
新增功能實現
- 多文件隊列傳輸功能
- 斷點續傳支持
三、實驗步驟
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.傳輸過程中突然關閉窗口會導致什么問題?如何解決?
????????傳輸過程中突然關閉窗口可能會導致數據傳輸不完整,文件損壞,或者服務端和客戶端之間的連接異常中斷。為了解決這個問題,通常可以采用以下措施:
-
實現數據包的確認機制,確保每個數據包都被正確接收。
-
使用斷點續傳功能,使得在傳輸中斷后可以從上次中斷的位置繼續傳輸。
-
設計良好的錯誤處理機制,能夠在檢測到連接中斷時嘗試恢復連接或通知用戶重新傳輸文件。