使用Python實現的局域網文件傳輸系統,包含發送端和接收端。

發送端代碼 (sender.py)

import socket
import os
import tqdm
import argparse

def send_file(host, port, file_path):
    """
    向指定主機發送文件
    """
    # 獲取文件大小
    file_size = os.path.getsize(file_path)
    filename = os.path.basename(file_path)
    
    # 創建socket對象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    print(f"[+] 正在連接到 {host}:{port}")
    s.connect((host, port))
    print("[+] 連接成功")
    
    # 發送文件名和文件大小
    file_info = f"{filename}|{file_size}"
    s.send(file_info.encode())
    
    # 等待確認
    response = s.recv(1024).decode()
    if response != "READY":
        print("[-] 接收端未準備就緒")
        s.close()
        return
    
    # 發送文件數據
    progress = tqdm.tqdm(range(file_size), f"發送 {filename}", unit="B", unit_scale=True, unit_divisor=1024)
    
    with open(file_path, "rb") as f:
        while True:
            # 讀取文件數據
            bytes_read = f.read(4096)
            if not bytes_read:
                # 文件傳輸完成
                break
            # 發送數據
            s.sendall(bytes_read)
            # 更新進度條
            progress.update(len(bytes_read))
    
    progress.close()
    print("[+] 文件發送完成")
    s.close()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="文件發送端")
    parser.add_argument("host", help="目標主機IP地址")
    parser.add_argument("file", help="要發送的文件路徑")
    parser.add_argument("-p", "--port", type=int, default=8888, help="端口號 (默認: 8888)")
    
    args = parser.parse_args()
    
    if not os.path.exists(args.file):
        print(f"[-] 文件 {args.file} 不存在")
        exit(1)
    
    send_file(args.host, args.port, args.file)

接收端代碼 (receiver.py)

import socket
import os
import tqdm

def start_server(host, port, save_dir):
    """
    啓動文件接收服務器
    """
    # 創建socket對象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 綁定地址和端口
    s.bind((host, port))
    
    # 開始監聽
    s.listen(5)
    print(f"[+] 服務器正在 {host}:{port} 上監聽...")
    
    while True:
        # 接受連接
        client_socket, address = s.accept()
        print(f"[+] 收到來自 {address} 的連接")
        
        # 接收文件信息
        file_info = client_socket.recv(1024).decode()
        filename, file_size = file_info.split("|")
        file_size = int(file_size)
        
        print(f"[+] 接收文件: {filename} ({file_size} 字節)")
        
        # 確認準備接收
        client_socket.send("READY".encode())
        
        # 確保保存目錄存在
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
        
        # 文件保存路徑
        file_path = os.path.join(save_dir, filename)
        
        # 接收文件數據
        progress = tqdm.tqdm(range(file_size), f"接收 {filename}", unit="B", unit_scale=True, unit_divisor=1024)
        
        with open(file_path, "wb") as f:
            received_bytes = 0
            while received_bytes < file_size:
                # 接收數據
                bytes_read = client_socket.recv(4096)
                if not bytes_read:
                    break
                # 寫入文件
                f.write(bytes_read)
                received_bytes += len(bytes_read)
                # 更新進度條
                progress.update(len(bytes_read))
        
        progress.close()
        print(f"[+] 文件已保存到: {file_path}")
        client_socket.close()

if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description="文件接收端")
    parser.add_argument("-H", "--host", default="0.0.0.0", help="監聽地址 (默認: 0.0.0.0)")
    parser.add_argument("-p", "--port", type=int, default=8888, help="監聽端口 (默認: 8888)")
    parser.add_argument("-d", "--dir", default="received_files", help="文件保存目錄 (默認: received_files)")
    
    args = parser.parse_args()
    
    start_server(args.host, args.port, args.dir)

圖形界面版本 (可選)

如果你想要圖形界面,可以使用以下代碼:

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import socket
import os
import tqdm

class FileTransferGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("局域網文件傳輸工具")
        self.root.geometry("500x400")
        
        self.setup_ui()
        
    def setup_ui(self):
        # 發送文件框架
        send_frame = ttk.LabelFrame(self.root, text="發送文件", padding=10)
        send_frame.pack(fill="x", padx=10, pady=5)
        
        ttk.Label(send_frame, text="目標IP:").grid(row=0, column=0, sticky="w", pady=5)
        self.target_ip = ttk.Entry(send_frame, width=20)
        self.target_ip.grid(row=0, column=1, pady=5, padx=5)
        self.target_ip.insert(0, "192.168.")
        
        ttk.Label(send_frame, text="端口:").grid(row=0, column=2, sticky="w", pady=5, padx=(20,0))
        self.port_send = ttk.Entry(send_frame, width=10)
        self.port_send.grid(row=0, column=3, pady=5, padx=5)
        self.port_send.insert(0, "8888")
        
        ttk.Label(send_frame, text="文件路徑:").grid(row=1, column=0, sticky="w", pady=5)
        self.file_path = ttk.Entry(send_frame, width=30)
        self.file_path.grid(row=1, column=1, columnspan=2, pady=5, padx=5, sticky="we")
        
        ttk.Button(send_frame, text="瀏覽", command=self.browse_file).grid(row=1, column=3, pady=5, padx=5)
        ttk.Button(send_frame, text="發送文件", command=self.send_file).grid(row=2, column=0, columnspan=4, pady=10)
        
        # 接收文件框架
        receive_frame = ttk.LabelFrame(self.root, text="接收文件", padding=10)
        receive_frame.pack(fill="x", padx=10, pady=5)
        
        ttk.Label(receive_frame, text="監聽IP:").grid(row=0, column=0, sticky="w", pady=5)
        self.listen_ip = ttk.Entry(receive_frame, width=20)
        self.listen_ip.grid(row=0, column=1, pady=5, padx=5)
        self.listen_ip.insert(0, "0.0.0.0")
        
        ttk.Label(receive_frame, text="端口:").grid(row=0, column=2, sticky="w", pady=5, padx=(20,0))
        self.port_receive = ttk.Entry(receive_frame, width=10)
        self.port_receive.grid(row=0, column=3, pady=5, padx=5)
        self.port_receive.insert(0, "8888")
        
        ttk.Label(receive_frame, text="保存目錄:").grid(row=1, column=0, sticky="w", pady=5)
        self.save_dir = ttk.Entry(receive_frame, width=30)
        self.save_dir.grid(row=1, column=1, columnspan=2, pady=5, padx=5, sticky="we")
        self.save_dir.insert(0, "received_files")
        
        ttk.Button(receive_frame, text="瀏覽", command=self.browse_dir).grid(row=1, column=3, pady=5, padx=5)
        
        self.start_server_btn = ttk.Button(receive_frame, text="啓動接收服務", command=self.toggle_server)
        self.start_server_btn.grid(row=2, column=0, columnspan=4, pady=10)
        
        self.server_running = False
        self.server_thread = None
        
        # 日誌框架
        log_frame = ttk.LabelFrame(self.root, text="日誌", padding=10)
        log_frame.pack(fill="both", expand=True, padx=10, pady=5)
        
        self.log_text = tk.Text(log_frame, height=10)
        scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
    
    def browse_file(self):
        filename = filedialog.askopenfilename()
        if filename:
            self.file_path.delete(0, tk.END)
            self.file_path.insert(0, filename)
    
    def browse_dir(self):
        directory = filedialog.askdirectory()
        if directory:
            self.save_dir.delete(0, tk.END)
            self.save_dir.insert(0, directory)
    
    def log(self, message):
        self.log_text.insert(tk.END, f"{message}\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()
    
    def send_file(self):
        host = self.target_ip.get()
        port = int(self.port_send.get())
        file_path = self.file_path.get()
        
        if not host or not file_path:
            messagebox.showerror("錯誤", "請填寫目標IP和文件路徑")
            return
        
        if not os.path.exists(file_path):
            messagebox.showerror("錯誤", "文件不存在")
            return
        
        # 在新線程中發送文件
        thread = threading.Thread(target=self._send_file, args=(host, port, file_path))
        thread.daemon = True
        thread.start()
    
    def _send_file(self, host, port, file_path):
        try:
            file_size = os.path.getsize(file_path)
            filename = os.path.basename(file_path)
            
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.log(f"[+] 正在連接到 {host}:{port}")
            s.connect((host, port))
            self.log("[+] 連接成功")
            
            file_info = f"{filename}|{file_size}"
            s.send(file_info.encode())
            
            response = s.recv(1024).decode()
            if response != "READY":
                self.log("[-] 接收端未準備就緒")
                s.close()
                return
            
            self.log(f"[+] 開始發送文件: {filename} ({file_size} 字節)")
            
            with open(file_path, "rb") as f:
                sent_bytes = 0
                while True:
                    bytes_read = f.read(4096)
                    if not bytes_read:
                        break
                    s.sendall(bytes_read)
                    sent_bytes += len(bytes_read)
                    progress = (sent_bytes / file_size) * 100
                    self.log(f"[+] 發送進度: {progress:.1f}%")
            
            self.log("[+] 文件發送完成")
            s.close()
        except Exception as e:
            self.log(f"[-] 發送文件時出錯: {str(e)}")
    
    def toggle_server(self):
        if not self.server_running:
            self.start_server()
        else:
            self.stop_server()
    
    def start_server(self):
        host = self.listen_ip.get()
        port = int(self.port_receive.get())
        save_dir = self.save_dir.get()
        
        if not host or not save_dir:
            messagebox.showerror("錯誤", "請填寫監聽IP和保存目錄")
            return
        
        self.server_running = True
        self.start_server_btn.config(text="停止接收服務")
        
        # 在新線程中啓動服務器
        self.server_thread = threading.Thread(target=self._start_server, args=(host, port, save_dir))
        self.server_thread.daemon = True
        self.server_thread.start()
        
        self.log(f"[+] 接收服務已啓動,監聽 {host}:{port}")
    
    def stop_server(self):
        self.server_running = False
        self.start_server_btn.config(text="啓動接收服務")
        self.log("[+] 接收服務已停止")
    
    def _start_server(self, host, port, save_dir):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind((host, port))
        s.listen(5)
        s.settimeout(1)  # 設置超時以便定期檢查server_running狀態
        
        while self.server_running:
            try:
                client_socket, address = s.accept()
                self.log(f"[+] 收到來自 {address} 的連接")
                
                # 在新線程中處理客户端連接
                client_thread = threading.Thread(
                    target=self._handle_client, 
                    args=(client_socket, address, save_dir)
                )
                client_thread.daemon = True
                client_thread.start()
            except socket.timeout:
                continue
            except Exception as e:
                if self.server_running:
                    self.log(f"[-] 服務器錯誤: {str(e)}")
        
        s.close()
    
    def _handle_client(self, client_socket, address, save_dir):
        try:
            file_info = client_socket.recv(1024).decode()
            filename, file_size = file_info.split("|")
            file_size = int(file_size)
            
            self.log(f"[+] 接收文件: {filename} ({file_size} 字節)")
            
            client_socket.send("READY".encode())
            
            if not os.path.exists(save_dir):
                os.makedirs(save_dir)
            
            file_path = os.path.join(save_dir, filename)
            
            with open(file_path, "wb") as f:
                received_bytes = 0
                while received_bytes < file_size:
                    bytes_read = client_socket.recv(4096)
                    if not bytes_read:
                        break
                    f.write(bytes_read)
                    received_bytes += len(bytes_read)
                    progress = (received_bytes / file_size) * 100
                    self.log(f"[+] 接收進度: {progress:.1f}%")
            
            self.log(f"[+] 文件已保存到: {file_path}")
            client_socket.close()
        except Exception as e:
            self.log(f"[-] 處理客户端 {address} 時出錯: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = FileTransferGUI(root)
    root.mainloop()

使用説明

命令行版本

  1. 安裝依賴:
pip install tqdm
  1. 在接收端運行:
python receiver.py -H 0.0.0.0 -p 8888 -d received_files
  1. 在發送端運行:
python sender.py 192.168.1.100 file.txt -p 8888

圖形界面版本

  1. 安裝依賴:
pip install tqdm
  1. 運行圖形界面:
python file_transfer_gui.py

注意事項

  1. 確保發送端和接收端在同一個局域網內
  2. 確保防火牆允許程序通過指定端口通信
  3. 大文件傳輸可能需要較長時間
  4. 圖形界面版本需要安裝tkinter(通常Python自帶)

這個實現提供了基本的文件傳輸功能,可以根據需要進行擴展,比如添加加密、壓縮、斷點續傳等功能。