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

Python Socket多文件传输失效问题求助(单文件可正常运行)

多文件Socket传输功能修复

问题说明

单文件传输功能正常,但多文件传输失效,项目为服务端向客户端发送文件,Tkinter仅用于获取选中文件路径。


原服务端代码

import socket
import os
import tkinter as tk
from tkinter import filedialog as fd

SERVER = socket.gethostbyname(socket.gethostname())
PORT = 1234
HEADER = 10

WINDOW_X = 600
WINDOW_Y = 350

root = tk.Tk()
root.title('MyCoolProgram')
root.resizable(False, False)
root.geometry(f'{WINDOW_X}x{WINDOW_Y}')

print(f'Binding {SERVER} with port {PORT}...')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((SERVER, PORT))

print(f'Listening...')
s.listen(5)

def send(files):
    con, addr = s.accept()
    print('Connected with ', addr)
    filedata = b''
    for file in files:

        head, tail = os.path.split(file)
        os.chdir(head)
        
        filename = tail
        filename = f'{len(filename):<{HEADER}}' + filename
        con.send(filename.encode())

        filesize = os.stat(tail).st_size
        filesize = f'{filesize:<{HEADER}}'
        filesize = filesize.encode()

        with open(tail, 'rb') as f:
            content = f.read()
            
        filedata += filesize + content + b'\0'

        con.send(filedata)

        con.close()
    


files = ''
T = tk.Text(root, height=18, width=73)
T.place(x=5, y=10)
T.config(state='disabled')

def openfiles(): 
    global files
    files = fd.askopenfilenames(parent=root, title='选择要发送的文件')

    T.config(state='normal')

    for file in files:
        T.insert('end',file + '\n')

    T.config(state='disabled')

def sendloop():
    global files
    send(files)
        

open_files_button = tk.Button(root, command=openfiles, text="选择文件", height=2, padx=7)
open_files_button.place(x=WINDOW_X - 130 , y = WINDOW_Y - 45)

send_files_button = tk.Button(root, command=sendloop, text="发送", height=2, padx=27)
send_files_button.place(x=WINDOW_X - 230 , y = WINDOW_Y - 45)

root.mainloop()

原客户端代码

import socket
import tqdm

HEADER = 10 
SERVER = "服务器公网IP" # 实际代码中为真实IP
BUFFER = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((SERVER, 1234))

print(f'已连接到 {SERVER}.')

while True:
    full_data = b''
    new = True

    name_data = []


    while True:
        data = s.recv(BUFFER)


        if new:
            
            datalen = int(data[:HEADER])
            new = False
            progress = tqdm.tqdm(unit='B',unit_scale=True, unit_divisor=1000, total=int(datalen))
            


        full_data += data

        progress.update(BUFFER)

        if len(full_data)-HEADER == datalen:
            acc_data = full_data[HEADER:]
            name_data.append(acc_data)
            new = True
            full_data = b''


        if len(name_data) == 2:
            with open(name_data[0], 'wb') as f:
                f.write(name_data[1])
            print(f'{name_data[0]} 已接收' )

            name_data = []
            new = True
            full_data = b''

问题分析

服务端核心问题

  1. 连接提前关闭:循环发送文件时,每发送一个文件就调用con.close(),导致第一个文件发送完成后连接断开,后续文件无法传输。
  2. 数据包逻辑混乱:filedata变量累加所有文件数据,且每次发送不重置,导致数据包包含冗余内容;同时文件名和文件数据的发送顺序与客户端接收逻辑不匹配。
  3. 切换目录风险:循环中调用os.chdir(head),若多个文件来自不同目录,会导致后续文件路径解析错误。

客户端核心问题

  1. 进度条逻辑错误:首次接收时用文件名长度作为进度条总长度,后续文件大小的处理逻辑缺失,导致进度条显示混乱。
  2. 多文件边界无标识:没有处理多文件传输结束的信号,客户端会一直等待数据,陷入死循环。
  3. 数据解析逻辑模糊:仅通过name_data长度判断是否完成一个文件的接收,没有明确区分文件名和文件内容的接收阶段。

修复后的代码

修复后服务端代码

import socket
import os
import tkinter as tk
from tkinter import filedialog as fd

SERVER = socket.gethostbyname(socket.gethostname())
PORT = 1234
HEADER = 10

WINDOW_X = 600
WINDOW_Y = 350

root = tk.Tk()
root.title('MyCoolProgram')
root.resizable(False, False)
root.geometry(f'{WINDOW_X}x{WINDOW_Y}')

print(f'绑定 {SERVER}:{PORT}...')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((SERVER, PORT))
s.listen(5)

def send(files):
    con, addr = s.accept()
    print('已连接客户端:', addr)
    
    # 先发送文件总数,让客户端知道要接收多少个文件
    file_count = f'{len(files):<{HEADER}}'
    con.send(file_count.encode())
    
    for file in files:
        head, tail = os.path.split(file)
        filename = tail
        
        # 发送文件名(带长度头)
        filename_len = f'{len(filename):<{HEADER}}'
        con.send(filename_len.encode() + filename.encode())
        
        # 获取文件大小并发送(带长度头)
        filesize = os.path.getsize(file)
        filesize_str = f'{filesize:<{HEADER}}'
        con.send(filesize_str.encode())
        
        # 分块发送文件内容,避免大文件一次性读取内存溢出
        with open(file, 'rb') as f:
            while True:
                chunk = f.read(1024)
                if not chunk:
                    break
                con.send(chunk)
        print(f'已发送文件: {filename}')
    
    # 发送结束标识,告诉客户端所有文件已发送完成
    con.send(b'END_TRANSFER')
    con.close()
    print('传输完成,连接已关闭')

files = ()
T = tk.Text(root, height=18, width=73)
T.place(x=5, y=10)
T.config(state='disabled')

def openfiles(): 
    global files
    files = fd.askopenfilenames(parent=root, title='选择要发送的文件')

    T.config(state='normal')
    T.delete(1.0, tk.END)
    for file in files:
        T.insert('end', file + '\n')
    T.config(state='disabled')

def sendloop():
    global files
    if files:
        send(files)
    else:
        print('请先选择要发送的文件')

open_files_button = tk.Button(root, command=openfiles, text="选择文件", height=2, padx=7)
open_files_button.place(x=WINDOW_X - 130 , y = WINDOW_Y - 45)

send_files_button = tk.Button(root, command=sendloop, text="发送", height=2, padx=27)
send_files_button.place(x=WINDOW_X - 230 , y = WINDOW_Y - 45)

root.mainloop()

修复后客户端代码

import socket
import tqdm

HEADER = 10 
SERVER = "服务器公网IP" # 替换为真实服务器IP
BUFFER = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((SERVER, 1234))
print(f'已连接到 {SERVER}')

# 先接收文件总数
file_count_data = s.recv(HEADER)
file_count = int(file_count_data.strip())
print(f'即将接收 {file_count} 个文件')

for _ in range(file_count):
    # 接收文件名长度和文件名
    filename_len_data = s.recv(HEADER)
    filename_len = int(filename_len_data.strip())
    filename = s.recv(filename_len).decode()
    
    # 接收文件大小
    filesize_data = s.recv(HEADER)
    filesize = int(filesize_data.strip())
    
    # 接收文件内容并写入本地
    progress = tqdm.tqdm(total=filesize, unit='B', unit_scale=True, unit_divisor=1000, desc=filename)
    received_size = 0
    with open(filename, 'wb') as f:
        while received_size < filesize:
            chunk = s.recv(min(BUFFER, filesize - received_size))
            if not chunk:
                break
            f.write(chunk)
            received_size += len(chunk)
            progress.update(len(chunk))
    progress.close()
    print(f'{filename} 接收完成')

# 接收结束标识
end_signal = s.recv(16).decode()
if end_signal == 'END_TRANSFER':
    print('所有文件接收完成')
s.close()

修复要点说明

  1. 服务端:

    • 先发送文件总数,让客户端明确接收任务量
    • 每个文件单独按「文件名长度头→文件名→文件大小头→文件内容」的顺序发送
    • 分块读取发送大文件,避免内存溢出
    • 所有文件发送完成后发送结束标识,最后关闭连接
    • 移除os.chdir,直接使用文件完整路径读取,避免目录切换错误
  2. 客户端:

    • 先接收文件总数,循环对应次数接收每个文件
    • 明确区分文件名、文件大小、文件内容的接收阶段
    • 进度条使用真实文件大小作为总长度,显示更准确
    • 接收完成后等待结束标识,然后关闭连接,避免死循环

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:20:28