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超时时间:
- 扩大本地端口范围:以管理员身份打开命令提示符,执行:
将动态端口范围设置为1024-65535(默认是49152-65535)。netsh int ipv4 set dynamicport tcp start=1024 num=64511 - 缩短TIME_WAIT超时:执行:
将netsh int tcp set global tcpfinwait2=30TIME_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
相关产品推荐
相关产品推荐

