Python Socket编程:实现服务器向多客户端批量发送文本文件需求
解决方案
核心修改内容
- 客户端新增
DOWNLOAD命令,支持从服务器获取文件 - 服务器新增
DOWNLOAD命令处理逻辑,同时维护在线客户端连接池以支持主动推送 - 服务器添加主动推送文件的功能,可指定目标客户端发送
- 所有通信通过服务器中转,确保客户端之间无直接连接
修改后的客户端代码(client.py)
import socket IP = "127.0.0.1" PORT = 4456 ADDR = (IP, PORT) FORMAT = "utf-8" SIZE = 1024 def main(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(ADDR) # 后台线程接收服务器消息,避免阻塞输入 def receive_messages(): while True: try: data = client.recv(SIZE).decode(FORMAT) if not data: break # 处理服务器主动推送的文件 if data.startswith("SEND@"): _, filename, text = data.split("@", 2) save_path = f"received_{filename}" with open(save_path, "w") as f: f.write(text) print(f"\n[SERVER PUSH] 文件 {filename} 已保存到 {save_path}") else: cmd, msg = data.split("@", 1) if cmd == "DISCONNECTED": print(f"\n[SERVER]: {msg}") break elif cmd == "OK": print(f"\n{msg}") except: break recv_thread = threading.Thread(target=receive_messages) recv_thread.daemon = True recv_thread.start() while True: data = input("> ") data = data.split(" ", 1) cmd = data[0] if cmd == "HELP": client.send(cmd.encode(FORMAT)) elif cmd == "LOGOUT": client.send(cmd.encode(FORMAT)) break elif cmd == "LIST": client.send(cmd.encode(FORMAT)) elif cmd == "DELETE": if len(data) < 2: print("请输入要删除的文件名:DELETE <filename>") continue client.send(f"{cmd}@{data[1]}".encode(FORMAT)) elif cmd == "UPLOAD": if len(data) < 2: print("请输入文件路径:UPLOAD <path>") continue path = data[1] try: with open(f"{path}", "r") as f: text = f.read() filename = path.split("/")[-1] send_data = f"{cmd}@{filename}@{text}" client.send(send_data.encode(FORMAT)) except FileNotFoundError: print("文件不存在,请检查路径") elif cmd == "DOWNLOAD": if len(data) < 2: print("请输入要下载的文件名:DOWNLOAD <filename>") continue client.send(f"{cmd}@{data[1]}".encode(FORMAT)) else: print("未知命令,输入HELP查看所有命令") print("Disconnected from the server.") client.close() if __name__ == "__main__": import threading main()
修改后的服务器代码(server.py)
import os import socket import threading IP = "127.0.0.1" PORT = 4456 ADDR = (IP, PORT) SIZE = 1024 FORMAT = "utf-8" SERVER_DATA_PATH = "server_data" # 在线客户端连接池,线程锁保证操作安全 online_clients = {} client_lock = threading.Lock() def handle_client(conn, addr): addr_str = f"{addr[0]}:{addr[1]}" print(f"[NEW CONNECTION] {addr_str} connected.") with client_lock: online_clients[addr_str] = conn conn.send("OK@Welcome to the File Server.".encode(FORMAT)) while True: try: data = conn.recv(SIZE).decode(FORMAT) if not data: break data = data.split("@", 2) cmd = data[0] if cmd == "LIST": files = os.listdir(SERVER_DATA_PATH) send_data = "OK@" send_data += "The server directory is empty" if len(files) == 0 else "\n".join(files) conn.send(send_data.encode(FORMAT)) elif cmd == "UPLOAD": if len(data) < 3: conn.send("OK@上传格式错误,请重新发送".encode(FORMAT)) continue name, text = data[1], data[2] filepath = os.path.join(SERVER_DATA_PATH, name) with open(filepath, "w") as f: f.write(text) conn.send("OK@File uploaded successfully.".encode(FORMAT)) elif cmd == "DELETE": if len(data) < 2: conn.send("OK@请指定要删除的文件名".encode(FORMAT)) continue filename = data[1] filepath = os.path.join(SERVER_DATA_PATH, filename) if os.path.exists(filepath): os.remove(filepath) send_data = "OK@File deleted successfully." else: send_data = "OK@File not found." conn.send(send_data.encode(FORMAT)) elif cmd == "DOWNLOAD": if len(data) < 2: conn.send("OK@请指定要下载的文件名".encode(FORMAT)) continue filename = data[1] filepath = os.path.join(SERVER_DATA_PATH, filename) if os.path.exists(filepath): with open(filepath, "r") as f: text = f.read() send_data = f"OK@{filename}@{text}" else: send_data = "OK@File not found on server." conn.send(send_data.encode(FORMAT)) elif cmd == "LOGOUT": break elif cmd == "HELP": help_text = "OK@LIST: List all the files from the server.\n" help_text += "UPLOAD <path>: Upload a file to the server.\n" help_text += "DELETE <filename>: Delete a file from the server.\n" help_text += "DOWNLOAD <filename>: Download a file from the server.\n" help_text += "LOGOUT: Disconnect from the server.\n" help_text += "HELP: List all the commands." conn.send(help_text.encode(FORMAT)) except Exception as e: print(f"[ERROR] {addr_str}: {e}") break # 移除断开的客户端连接 with client_lock: if addr_str in online_clients: del online_clients[addr_str] print(f"[DISCONNECTED] {addr_str} disconnected") conn.close() def server_input_handler(): """服务器端命令处理,用于主动推送文件""" while True: cmd = input("\n服务器命令 > ") cmd_parts = cmd.split(" ", 2) if not cmd_parts: continue main_cmd = cmd_parts[0].upper() if main_cmd == "SEND": if len(cmd_parts) < 3: print("格式:SEND <客户端地址> <文件名>") print("在线客户端:", list(online_clients.keys())) continue target_addr, filename = cmd_parts[1], cmd_parts[2] if target_addr not in online_clients: print("客户端不在线或地址错误") print("在线客户端:", list(online_clients.keys())) continue filepath = os.path.join(SERVER_DATA_PATH, filename) if not os.path.exists(filepath): print("服务器上不存在该文件") continue with open(filepath, "r") as f: text = f.read() try: online_clients[target_addr].send(f"SEND@{filename}@{text}".encode(FORMAT)) print(f"已向 {target_addr} 推送文件 {filename}") except Exception as e: print(f"推送失败:{e}") with client_lock: del online_clients[target_addr] elif main_cmd == "LIST_CLIENTS": print("在线客户端:", list(online_clients.keys())) elif main_cmd == "HELP_SERVER": print("服务器命令列表:") print("SEND <客户端地址> <文件名>: 向指定客户端推送文件") print("LIST_CLIENTS: 查看在线客户端") print("HELP_SERVER: 查看服务器命令") if __name__ == "__main__": # 创建服务器存储目录 if not os.path.exists(SERVER_DATA_PATH): os.makedirs(SERVER_DATA_PATH) print("[STARTING] Server is starting") server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(ADDR) server.listen() print(f"[LISTENING] Server is listening on {IP}:{PORT}.") # 启动服务器命令处理线程 input_thread = threading.Thread(target=server_input_handler) input_thread.daemon = True input_thread.start() while True: conn, addr = server.accept() thread = threading.Thread(target=handle_client, args=(conn, addr)) thread.start() print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 2}")
功能说明
客户端功能
- 支持
DOWNLOAD <filename>命令从服务器下载文件,自动保存为received_<filename> - 后台线程实时接收服务器消息,可处理服务器主动推送的文件
- 优化命令输入校验,避免参数缺失导致的报错
- 支持
服务器功能
- 维护在线客户端连接池,支持主动向指定客户端推送文件
- 新增服务器端命令:
LIST_CLIENTS:查看当前在线客户端列表SEND <客户端地址> <文件名>:向指定客户端推送服务器文件HELP_SERVER:查看服务器端可用命令
- 替换不安全的
os.system(rm)为os.remove(),提升操作安全性 - 使用线程锁保证连接池操作的线程安全,避免多线程冲突
客户端隔离
所有客户端通信均通过服务器中转,客户端之间无直接TCP连接,完全满足“客户端之间不能直接通信”的要求
内容的提问来源于stack exchange,提问作者Akash Biswas
相关产品推荐
相关产品推荐

