Python Sockets问题:客户端请求指定文件后接收阶段挂起
我之前也踩过这个Socket文件传输的坑!客户端收完文件还挂着,本质就是客户端不知道什么时候该停止接收数据,一直在recv()那里阻塞等待。咱们一步步来解决这个问题,先分析核心原因,再给出修正后的代码。
核心问题:TCP流的“边界模糊”
TCP是基于流的协议,不像UDP有数据包边界。客户端的recv(1024)会一直阻塞,直到收到数据或者连接关闭。如果服务器发送完文件后没明确告诉客户端“我发完了”,客户端就会傻等下去。
常见的解决方案有两种:
方案1:先发送文件大小,客户端按大小接收(推荐,支持长连接)
这种方式最可靠,客户端知道要接收多少字节,收够就停止,不用依赖连接关闭。
修改后的server.py
import socket import os def main(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 12345)) server_socket.listen(1) print("等待客户端连接...") conn, addr = server_socket.accept() print(f"连接来自 {addr}") # 接收客户端发送的文件名 filename = conn.recv(1024).decode('utf-8') print(f"请求文件: {filename}") if os.path.exists(filename): # 第一步:发送文件大小给客户端 file_size = os.path.getsize(filename) conn.send(str(file_size).encode('utf-8')) # 等待客户端确认(防止粘包,可选但更严谨) conn.recv(1024) # 第二步:分块发送文件内容 with open(filename, 'rb') as f: while True: chunk = f.read(1024) if not chunk: break conn.send(chunk) print("文件发送完成") else: # 发送特殊标记表示文件不存在 conn.send(b'-1') print("文件不存在") conn.close() server_socket.close() if __name__ == "__main__": main()
修改后的client.py
import socket def main(): client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(('localhost', 12345)) # 发送要请求的文件名 filename = input("请输入要请求的文件名: ") client_socket.send(filename.encode('utf-8')) # 第一步:接收文件大小 file_size_str = client_socket.recv(1024).decode('utf-8') if file_size_str == '-1': print("服务器上不存在该文件") client_socket.close() return file_size = int(file_size_str) # 发送确认,告诉服务器可以开始发文件了 client_socket.send(b'OK') # 第二步:按大小接收文件内容 received_bytes = 0 save_filename = f'received_{filename}' with open(save_filename, 'wb') as f: while received_bytes < file_size: # 每次接收的字节数不超过剩余需要接收的量,避免冗余 chunk_size = min(1024, file_size - received_bytes) data = client_socket.recv(chunk_size) if not data: break # 极端情况:连接中途断开 f.write(data) received_bytes += len(data) print(f"已接收: {received_bytes}/{file_size} 字节") print(f"文件接收完成,已保存为 {save_filename}") client_socket.close() if __name__ == "__main__": main()
方案2:服务器发送完文件后主动关闭连接(适合短连接)
如果你的场景是一次连接只传一个文件,也可以让服务器发送完文件后直接关闭连接,客户端收到空数据(recv()返回b'')就停止接收:
简化版server.py(仅修改发送文件部分)
# ... 前面代码不变 ... with open(filename, 'rb') as f: while True: chunk = f.read(1024) if not chunk: break conn.send(chunk) print("文件发送完成") conn.close() # 发送完就关闭连接 # ... 后面代码不变 ...
简化版client.py(接收部分)
# ... 前面代码不变 ... save_filename = f'received_{filename}' with open(save_filename, 'wb') as f: while True: data = client_socket.recv(1024) if not data: # 连接关闭时会返回空数据 break f.write(data) print(f"文件接收完成,已保存为 {save_filename}") client_socket.close()
关键注意点
- 粘包问题:方案1里的“客户端确认”步骤是为了避免文件大小和文件内容粘在一起(比如服务器连续发送
file_size和文件内容,客户端一次recv()可能把两者都收了)。如果你的文件很小,也可以忽略,但加上更稳妥。 - 异常处理:实际项目中建议加上
try-except块,处理连接中断、文件读写错误等情况。
内容的提问来源于stack exchange,提问作者Adam Shick
相关产品推荐
相关产品推荐

