TCP跨客户端文件传输遇ConnectionResetError及接收不全问题求助
问题描述
我的架构是先建立UDP连接,再通过TCP实现客户端间的文件传输——由TCP客户端向作为TCP服务端的另一客户端发起文件请求,完成文件发送与下载。目前发送端运行正常,但接收端存在问题:要么无法完整接收文件,要么抛出ConnectionResetError异常,有时文件延迟接收完成后仍触发该异常。需要适配任意大小文件传输,纠结接收端是否该用while循环,求解决方案。
报错信息
Exception in thread Thread-5 (file_receiving_thread): Traceback (most recent call last): File "/opt/homebrew/Cellar/python@3.11/3.11.2_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 1038, in _bootstrap_inner self.run() File "/opt/homebrew/Cellar/python@3.11/3.11.2_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 975, in run self._target(*self._args, **self._kwargs) File "/App.py", line 205, in file_receiving_thread data = s.recvfrom(4096) ^^^^^^^^^^^^^^^^ ConnectionResetError: [Errno 54] Connection reset by peer < Connection with client A closed. >
问题根源分析
- TCP套接字误用UDP方法:接收端用了
recvfrom()(UDP专属方法)处理TCP连接,TCP应使用recv()。 - 发送端一次性读取大文件:
f.read()会把整个大文件加载到内存,既低效又可能导致发送不完整,同时未处理TCP粘包/拆包问题。 - 连接关闭时机错误:发送端线程结束后立即关闭连接,此时接收端可能还在读取数据,直接触发
ConnectionResetError;接收端无文件传输结束标识,会一直阻塞等待数据。 - 缺少完整性校验:未在传输前发送文件大小,接收端无法判断是否接收完整。
修复后的代码
接收端线程(修正TCP方法+按文件大小接收)
import time def file_receiving_thread(s, fileName): print(f"< Downloading {fileName} ... >") # 先接收文件大小(8字节大端序表示64位整数) file_size_data = b'' while len(file_size_data) < 8: chunk = s.recv(8 - len(file_size_data)) if not chunk: print("< 连接中断,文件接收失败 >") return file_size_data += chunk file_size = int.from_bytes(file_size_data, byteorder='big') received_size = 0 with open(f'downloaded_{fileName}', 'wb') as f: while received_size < file_size: # 每次接收不超过4096字节的块,适配大文件 chunk_size = min(4096, file_size - received_size) data = s.recv(chunk_size) if not data: print("< 连接中断,文件接收不完整 >") return f.write(data) received_size += len(data) print(f'< {fileName} downloaded successfully! 已接收{received_size}/{file_size}字节 >')
发送端线程(分块发送+先传文件大小)
def file_sender_thread(tcp_conn, tcp_client_address, requested_file_name_decoded): global my_dir_path global myTbl # 检查文件是否存在 file_path = os.path.join(my_dir_path, requested_file_name_decoded) absolute_file_path = os.path.abspath(file_path) if not os.path.isfile(absolute_file_path): print(f"< {requested_file_name_decoded} not found in directory {my_dir_path} >") tcp_conn.sendall(b'ERROR: File not found') tcp_conn.close() return try: # 获取并发送文件大小 file_size = os.path.getsize(absolute_file_path) tcp_conn.sendall(file_size.to_bytes(8, byteorder='big')) with open(absolute_file_path, 'rb') as f: print(f'< Transferring {requested_file_name_decoded}... 总大小{file_size}字节 >') while True: # 分块读取文件,每次4096字节 data = f.read(4096) if not data: break # 确保所有数据发送完成 tcp_conn.sendall(data) print(f'< {requested_file_name_decoded} transferred successfully! >') except Exception as e: print(f'传输错误: {e} ({tcp_client_address})') tcp_conn.sendall(b'ERROR: Transfer failed') finally: # 延迟关闭连接,给接收端足够时间读取最后一批数据 time.sleep(0.5) tcp_conn.close()
TCP监听线程(修正TCP数据读取逻辑)
def tcp_listen_for_thread(my_tcp_port): tcp_socket = socket(AF_INET, SOCK_STREAM) tcp_socket.bind(('localhost', my_tcp_port)) tcp_socket.listen() print(f"< TCP服务端已启动,监听端口{my_tcp_port} >") while True: tcp_conn, tcp_client_address = tcp_socket.accept() print(f"< Accepting connection request from {tcp_client_address} >") # TCP用recv()读取文件名,替代UDP的recvfrom() requested_file_name_data = b'' chunk = tcp_conn.recv(4096) if chunk: requested_file_name_data += chunk requested_file_name_decoded = requested_file_name_data.decode().strip() # 启动发送线程,不阻塞监听逻辑 file_to_send_thread = threading.Thread(target=file_sender_thread, args=(tcp_conn, tcp_client_address, requested_file_name_decoded,)) file_to_send_thread.start() print(f'< 已为客户端{tcp_client_address}启动文件发送线程 >')
TCP请求线程(优化连接关闭逻辑)
def tcp_talk_thread(fileName, fileOwner, owners_tcp_IP, owners_tcp_port): try: with socket(AF_INET, SOCK_STREAM) as s: s.connect((owners_tcp_IP, owners_tcp_port)) print(f"< Connection with client {fileOwner} established. >") # 发送文件名 s.sendall(fileName.encode()) # 启动接收线程并等待完成 file_to_receive_thread = threading.Thread(target=file_receiving_thread, args=(s, fileName,)) file_to_receive_thread.start() file_to_receive_thread.join() print(f'< Connection with client {fileOwner} closed. >') except Exception as e: print(f'< 连接或接收错误: {e} >')
关键修复点总结
- TCP/UDP方法区分:TCP套接字统一使用
recv()和sendall(),避免误用UDP的recvfrom()。 - 分块传输:发送和接收都采用4096字节固定块,适配任意大小文件,避免内存溢出。
- 文件大小预传输:发送前先传递文件大小,接收端可精确判断传输是否完成,避免无限循环。
- 连接关闭优化:发送端传输完成后延迟关闭连接,确保接收端读取完所有数据。
- 异常处理增强:增加传输过程中的异常捕获,及时反馈错误信息。
内容的提问来源于stack exchange,提问作者Oscar5
相关产品推荐
相关产品推荐

