如何在Python 3中基于服务器中转实现客户端间文件传输
Hey there! Let's tackle this file transfer feature for your client-server chat app. I see you've already got the basic client communication working, so we'll build on that with a clear protocol for file transfers and tweak your existing functions to handle input paths, chunked sending, and proper file reassembly.
Step 1: Define a File Transfer Protocol
First, we need a way to distinguish regular chat messages from file transfer data. Let's use a simple pipe-separated format to avoid confusion:
- For file content chunks:
FILE_CHUNK|{filename}|{content}|{signature} - To signal the end of a file:
FILE_END|{filename}
This helps the receiving client recognize when it's receiving a file, track its name, and know when to stop writing to disk.
Step 2: Update the Client's Send Logic
Modify the send function to detect when the user enters a file transfer command (like /send /path/to/your/file.txt). We'll parse the input path, validate the file exists, then trigger the updated send_file function.
Step 3: Improve the send_file Function
Instead of reading line-by-line (which breaks for files without newlines), we'll read in chunks matching your BUFSIZ value. We'll also attach the protocol headers and your signature to each chunk to maintain integrity.
Step 4: Enhance the receive Function to Handle Files
Update the receive loop to check for our file transfer markers. When a chunk comes in, we'll start writing to the specified file, and when we get the FILE_END signal, we'll close the file and notify the user.
Modified Client Code
Here's the updated client script with all these changes integrated:
#!/usr/bin/env python3 """Script for Tkinter GUI chat client with file transfer.""" from socket import AF_INET, socket, SOCK_STREAM from threading import Thread import tkinter import os # Replace this with your actual signing key initialization signing_key = None # Example: signing_key = ed25519.SigningKey(b"your_private_key_here") def receive(): """Handles receiving of messages and file transfers.""" current_file = None current_filename = None while True: try: msg = client_socket.recv(BUFSIZ).decode("utf8") # Handle file chunks if msg.startswith("FILE_CHUNK|"): _, filename, content, signature = msg.split("|", 3) # Optional: Add your signature verification logic here # if not verify_signature(content, signature): # msg_list.insert(tkinter.END, f"⚠️ Invalid signature for {filename}") # continue if not current_file: current_filename = filename current_file = open(filename, "wb") # Open in binary mode for broader support current_file.write(content.encode("utf8", errors="replace")) # Handle end of file transfer elif msg.startswith("FILE_END|"): _, filename = msg.split("|", 1) if current_file: current_file.close() current_file = None msg_list.insert(tkinter.END, f"✅ File {filename} received successfully!") # Handle regular chat messages else: msg_list.insert(tkinter.END, msg) except OSError: # Clean up if client disconnects mid-transfer if current_file: current_file.close() break except Exception as e: msg_list.insert(tkinter.END, f"❌ Error receiving file: {str(e)}") if current_file: current_file.close() current_file = None def send(event=None): """Handles sending of messages and file transfer commands.""" msg_input = my_msg.get() my_msg.set("") # Clear input field # Check for file transfer command if msg_input.startswith("/send "): parts = msg_input.split(maxsplit=1) if len(parts) != 2: msg_list.insert(tkinter.END, "⚠️ Usage: /send <file_path>") return file_path = parts[1] # Validate file path if not os.path.exists(file_path): msg_list.insert(tkinter.END, f"⚠️ File {file_path} does not exist!") return if not os.path.isfile(file_path): msg_list.insert(tkinter.END, f"⚠️ {file_path} is not a valid file!") return # Notify chat about the file transfer client_socket.send(bytes(f"📤 Sending file: {os.path.basename(file_path)}", "utf8")) send_file(file_path) else: # Send regular chat message client_socket.send(bytes(msg_input, "utf8")) if msg_input == "{quit}": client_socket.close() top.quit() def on_closing(event=None): """Triggered when the window is closed.""" my_msg.set("{quit}") send() def send_file(file_path): """Sends a file in chunks with protocol headers and signatures.""" filename = os.path.basename(file_path) try: with open(file_path, "rb") as file: while True: # Calculate chunk size to fit within BUFSIZ after adding headers max_chunk_size = BUFSIZ - len(f"FILE_CHUNK|{filename}||".encode("utf8")) chunk = file.read(max_chunk_size) if not chunk: break content = chunk.decode("utf8", errors="replace") # Generate signature for the chunk signature = signing_key.sign(content.encode(), encoding="hex").decode() # Build and send the chunk message chunk_msg = f"FILE_CHUNK|{filename}|{content}|{signature}" client_socket.send(bytes(chunk_msg, "utf8")) # Send end signal after all chunks are sent client_socket.send(bytes(f"FILE_END|{filename}", "utf8")) except Exception as e: msg_list.insert(tkinter.END, f"❌ Error sending file: {str(e)}") top = tkinter.Tk() top.title("Chatter with File Transfer") messages_frame = tkinter.Frame(top) my_msg = tkinter.StringVar() scrollbar = tkinter.Scrollbar(messages_frame) msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set) scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH) msg_list.pack() messages_frame.pack() entry_field = tkinter.Entry(top, textvariable=my_msg) entry_field.bind("<Return>", send) entry_field.pack() send_button = tkinter.Button(top, text="Send", command=send) send_button.pack() top.protocol("WM_DELETE_WINDOW", on_closing) # Socket setup HOST = input('Enter host: ') PORT = input('Enter port: ') if not PORT: PORT = 33000 else: PORT = int(PORT) BUFSIZ = 1024 ADDR = (HOST, PORT) client_socket = socket(AF_INET, SOCK_STREAM) client_socket.connect(ADDR) receive_thread = Thread(target=receive) receive_thread.start() tkinter.mainloop()
Key Changes Explained
- Protocol Markers:
FILE_CHUNKandFILE_ENDlet the client differentiate file data from regular messages. - Chunked Reading: Uses
read(BUFSIZ)instead ofreadline()to support all file types (including those without newlines). - Input Validation: Checks if the specified file exists and is a valid file before attempting transfer.
- File Reassembly: Tracks the active file being received, writes chunks to disk, and cleans up when the transfer ends.
- Error Handling: Adds safeguards for missing files, transfer interruptions, and general read/write errors.
Quick Notes
- Make sure to initialize your
signing_keywith your actual private key (I left a placeholder for this). - For true binary file support (like images or executables), encode chunks in base64 before sending and decode them on receipt—this avoids issues with non-UTF8 bytes.
- Your existing server code doesn't need any changes—it will automatically forward file chunks to all connected clients. If you want to send files to specific users, you'd need to add user targeting logic to the protocol and server.
内容的提问来源于stack exchange,提问作者user9580162

