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

Python Socket/Threads 服务端向客户端发送文件功能报错排查

问题根因排查

当前文件传输功能报错由3个核心逻辑错误导致,和你预判的两个问题完全吻合,另有一处流程顺序问题:

  • 第一,sendFile函数未获取目标客户端的连接对象,代码里误用了服务端监听新连接用的theSocket发送数据,这个socket不承担和已连接客户端的通信任务,调用send方法直接抛异常,正好触发外层的Error sending the command !捕获逻辑。
  • 第二,二进制读写与socket编解码逻辑完全混乱:用rb模式读出来的文件内容本身就是bytes类型,不能再调用.encode("utf-8");代码中甚至尝试给文件句柄对象fileSending调用encode方法,该对象不是字符串,这行代码必报错。反过来客户端接收二进制文件内容时,也不能先decode成utf-8再写入wb模式的文件,非文本类文件(图片、压缩包、可执行程序)不符合utf-8编码规则,转码会直接报错或损坏文件。
  • 第三,收发流程顺序错位:服务端发送Send指令后未等待客户端就绪确认就直接发数据,客户端收到Send指令后还会把该字符串当成系统命令传给subprocess执行,既会产生无效操作,还会打乱socket字节流的顺序。另外现有代码仅调用一次recv(1024)接收数据,只要文件体积超过1024字节就会传输不全。
修复方案
  1. 服务端调整:给sendFile传入目标客户端连接对象,所有文件相关数据都走该连接传输,禁止使用监听socket发业务数据;文件名单独编码发送,等客户端返回就绪确认后,循环读取文件二进制块传输,不要一次性把大文件读入内存,也不要做多余的编码操作;文件发完后传输固定结束标记,等客户端返回接收完成响应后再回到命令交互循环;调整指令发送逻辑,Send指令触发文件传输后直接进入下一轮循环,不要重复发送指令。
  2. 客户端调整:收到Send指令后直接跳转文件接收逻辑,不要把Send传给subprocess执行;区分文本控制指令和二进制文件内容,二进制内容直接写入文件,不做utf-8转码;循环接收数据直到匹配到结束标记,保证大文件接收完整;每一步状态变更都给服务端返回响应,对齐收发顺序。
修复后核心代码

服务端修改部分

# 定义固定常量,避免魔法值
FILE_END_FLAG = b"__FILE_TRANSFER_DONE__"
BUFFER_SIZE = 4096

def sendFile(target_conn):
    file_path = "Profiles.txt"
    import os
    # 先校验本地文件是否存在
    if not os.path.exists(file_path):
        print(f"文件 {file_path} 不存在")
        target_conn.send(b"FILE_NOT_EXIST")
        return
    file_name = os.path.basename(file_path)
    # 发送文件名
    target_conn.send(file_name.encode("utf-8"))
    # 等待客户端就绪
    ready_msg = target_conn.recv(BUFFER_SIZE)
    if ready_msg != b"READY_TO_RECV":
        print("客户端未准备好接收文件")
        return
    # 循环读文件二进制块发送
    with open(file_path, "rb") as f:
        while True:
            chunk = f.read(BUFFER_SIZE)
            if not chunk:
                break
            target_conn.send(chunk)
    # 发送传输结束标记
    target_conn.send(FILE_END_FLAG)
    # 等待客户端接收完成确认
    done_msg = target_conn.recv(BUFFER_SIZE)
    if done_msg == b"RECV_DONE":
        print(f"文件 {file_name} 发送成功")

def sendTargetCommands(connection) :
    while True :
        try :
            cmd = input()
            if cmd == "Quit" :
                break
        
            if cmd == "Send" :
                # 发送Send指令通知客户端准备接收
                connection.send(str.encode(cmd))
                resp = connection.recv(BUFFER_SIZE)
                if resp == b"START_RECV":
                    # 传入目标连接调用文件发送逻辑
                    sendFile(connection)
                # 传输完成后重置命令行提示符,进入下一轮循环
                print(str(addressesList[connectionsList.index(connection)][0]) + "> ", end="")
                continue

            # 普通命令发送逻辑
            if len(str.encode(cmd)) > 0 :
                connection.send(str.encode(cmd))
                clientResponse = str(connection.recv(20480), "utf-8")
                print(clientResponse, end="")

        except Exception as e:
            # 打印具体异常信息方便调试,不要裸捕获吞掉错误
            print(f"Error sending the command ! 错误详情: {str(e)}")
            break

注:原有listConnections函数存在遍历列表同时删除元素的索引错位问题,遍历过程中直接del列表元素会导致部分连接漏检测,建议改成倒序遍历,或新建列表存储有效连接后替换原列表。

客户端修改部分

FILE_END_FLAG = b"__FILE_TRANSFER_DONE__"
BUFFER_SIZE = 4096

def recieveFile ():
    print("开始接收文件...")
    # 通知服务端已进入接收流程
    theSocket.send(b"START_RECV")
    # 接收文件名
    file_name = theSocket.recv(BUFFER_SIZE).decode("utf-8")
    if file_name == "FILE_NOT_EXIST":
        print("服务端待发送文件不存在")
        return
    print(f"正在接收文件: {file_name}")
    # 返回就绪确认
    theSocket.send(b"READY_TO_RECV")
    # 循环接收文件二进制内容
    with open(file_name, "wb") as f:
        while True:
            chunk = theSocket.recv(BUFFER_SIZE)
            # 检测到结束标记后,写入标记前的有效内容并退出循环
            if chunk.endswith(FILE_END_FLAG):
                f.write(chunk[:-len(FILE_END_FLAG)])
                break
            f.write(chunk)
    # 返回接收完成确认
    theSocket.send(b"RECV_DONE")
    print(f"文件 {file_name} 接收完成,已保存到本地")

def commandsReceiving() :
    while True :
        dataReceiver = theSocket.recv(BUFFER_SIZE)
        # 空数据代表连接断开
        if not dataReceiver:
            print("与服务端连接已断开")
            break
        # 识别到Send指令直接进入文件接收流程,不执行后续命令逻辑
        cmd_text = dataReceiver.decode("utf-8", errors="ignore")
        if cmd_text == "Send" :
            recieveFile()
            continue
        # 处理cd命令
        if cmd_text[:2] == 'cd':
            try:
                os.chdir(cmd_text[3:])
            except:
                pass
            currentDirectory = os.getcwd() + "> "
            theSocket.send(str.encode(currentDirectory))
            continue
        # 处理普通shell命令
        if len(dataReceiver) > 0 :
            cmd = subprocess.Popen(cmd_text,shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)
            outputByte = cmd.stdout.read() + cmd.stderr.read()
            outputString = str(outputByte, "utf-8", errors="ignore")
            currentDirectory = os.getcwd() + "> "
            theSocket.send(str.encode(outputString + currentDirectory))
            print(outputString)
额外注意事项
  • socket是流式传输协议,不要假设一次recv就能收完所有发送的数据,不管是命令还是文件,都要通过循环接收或约定结束标记的方式保证数据完整性,避免粘包、数据截断问题。
  • 二进制数据传输全程不要做utf-8编解码,只有控制类文本(指令、文件名、状态标记)需要做编解码,否则非文本文件传输后必损坏。
  • 调试阶段不要用无参数的except直接捕获所有异常后只打印通用报错,输出具体异常信息能大幅缩短问题定位时间。

内容的提问来源于stack exchange,提问作者Soufiane El Himani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 00:06:13