You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}")

功能说明

  1. 客户端功能

    • 支持DOWNLOAD <filename>命令从服务器下载文件,自动保存为received_<filename>
    • 后台线程实时接收服务器消息,可处理服务器主动推送的文件
    • 优化命令输入校验,避免参数缺失导致的报错
  2. 服务器功能

    • 维护在线客户端连接池,支持主动向指定客户端推送文件
    • 新增服务器端命令:
      • LIST_CLIENTS:查看当前在线客户端列表
      • SEND <客户端地址> <文件名>:向指定客户端推送服务器文件
      • HELP_SERVER:查看服务器端可用命令
    • 替换不安全的os.system(rm)为os.remove(),提升操作安全性
    • 使用线程锁保证连接池操作的线程安全,避免多线程冲突
  3. 客户端隔离
    所有客户端通信均通过服务器中转,客户端之间无直接TCP连接,完全满足“客户端之间不能直接通信”的要求


内容的提问来源于stack exchange,提问作者Akash Biswas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:47:09