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

Python多线程分片传大文件遇WinError 10048错误求助

解决WinError 10048:大文件分片多线程上传端口耗尽问题

问题原因

你遇到的WinError 10048是因为短时间内创建了大量TCP客户端连接,每个连接都会占用一个本地端口,而Windows系统中处于TIME_WAIT状态的端口默认需要240秒才能被重新使用。大文件分片数多(500MB文件按32KB分片约有15625个分片),每个分片任务都新建并关闭一个socket,很快就会耗尽可用的本地端口,导致无法创建新连接。

注意:SO_REUSEADDR选项仅适用于监听端口(服务器端),对客户端连接的端口复用没有帮助,所以当前代码中的这个设置无法解决问题。

解决方案

1. 实现Socket连接池(代码层面最优解)

通过复用固定数量的socket连接,避免每个分片都新建连接。线程池中的线程从连接池获取socket,发送分片后放回,整个上传过程仅创建与线程数相等的socket,彻底解决端口耗尽问题。

修改后的核心代码示例:

import os
import socket
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
import tkinter.messagebox as messagebox
import tkinter.filedialog as filedialog

class FileUploader:
    def __init__(self, host, port, threads=5):
        self.host = host
        self.port = port
        self.fragment_size = 1024 * 32
        self.threads = threads

    def upload_file(self):
        file_name = filedialog.askopenfilename(title="Select file to upload")
        if file_name:
            with ThreadPoolExecutor(max_workers=self.threads) as executor:
                executor.submit(self.send_file, file_name)

    def send_metadata(self, filename, file_size, fragment_count):
        # 保留你原有的元数据发送逻辑,此处为示例简化
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((self.host, self.port))
            sock.sendall(os.path.basename(filename).encode('utf-8') + b'\x00')
            sock.sendall(file_size.to_bytes(8, byteorder='big'))
            sock.sendall(fragment_count.to_bytes(4, byteorder='big'))
            sock.close()
            return True
        except:
            return False

    def send_file(self, filename):
        try:
            file_size = os.path.getsize(filename)
            fragment_count = (file_size // self.fragment_size) + (1 if file_size % self.fragment_size != 0 else 0)

            if not self.send_metadata(filename, file_size, fragment_count):
                raise Exception("Failed to send metadata.")

            # 初始化socket连接池,大小等于线程数
            socket_pool = Queue(maxsize=self.threads)
            for _ in range(self.threads):
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect((self.host, self.port))
                socket_pool.put(sock)

            def send_fragment_with_pool(fragment, fragment_number, fragment_count):
                sock = socket_pool.get()
                try:
                    # 替换为你原有的send_fragment发送逻辑
                    sock.sendall(b'FRAG')  # 分片标识
                    sock.sendall(fragment_number.to_bytes(4, byteorder='big'))
                    sock.sendall(fragment_count.to_bytes(4, byteorder='big'))
                    sock.sendall(len(fragment).to_bytes(4, byteorder='big'))
                    sock.sendall(fragment)
                    # 可选:等待服务器确认分片接收
                    ack = sock.recv(4)
                    if ack != b'OK':
                        raise Exception("Server did not acknowledge fragment")
                    # 放回连接池供后续任务使用
                    socket_pool.put(sock)
                except Exception as e:
                    # 连接出错时重建socket并放回池
                    sock.close()
                    new_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    new_sock.connect((self.host, self.port))
                    socket_pool.put(new_sock)
                    raise e

            with open(filename, 'rb') as file:
                with ThreadPoolExecutor(max_workers=self.threads) as executor:
                    futures = []
                    for fragment_number in range(fragment_count):
                        fragment = file.read(self.fragment_size)
                        futures.append(executor.submit(send_fragment_with_pool, fragment, fragment_number, fragment_count))

                    # 等待所有分片上传完成
                    for future in futures:
                        future.result()

            # 上传完成后关闭所有socket连接
            while not socket_pool.empty():
                sock = socket_pool.get()
                sock.close()

        except Exception as e:
            messagebox.showerror("Error", f"Failed to upload file: {e}")

2. 调整Windows TCP系统参数(辅助方案)

如果无法修改代码,可以通过调整系统参数扩大端口范围或缩短TIME_WAIT超时时间:

  • 扩大本地端口范围:以管理员身份打开命令提示符,执行:
    netsh int ipv4 set dynamicport tcp start=1024 num=64511
    
    将动态端口范围设置为1024-65535(默认是49152-65535)。
  • 缩短TIME_WAIT超时:执行:
    netsh int tcp set global tcpfinwait2=30
    
    将TIME_WAIT超时从默认240秒改为30秒,加快端口回收。

3. 谨慎使用SO_LINGER(不推荐)

设置SO_LINGER选项让socket关闭时立即释放端口,但可能导致未发送的数据丢失,仅适合对数据完整性要求不高的场景:

import struct
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置linger参数:开启linger,超时0秒(立即关闭连接)
linger = struct.pack('ii', 1, 0)
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, linger)
client_socket.connect((self.host, self.port))

关键说明

  • 连接池方案既满足“每个分片对应一个线程发送”的要求,又避免了端口耗尽问题,是最可靠的解决方案。
  • 调整系统参数需要管理员权限,且会影响整个系统的TCP行为,仅作为临时辅助手段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:45:07