Socket双向传输HDF5文件时接收端无报错挂起问题排查
问题原因分析
程序挂起的核心原因是TCP流式传输的边界问题,两端均陷入阻塞等待:
服务器端卡在接收循环:
服务器的while True: chunk = conn.recv(1024)会持续运行,直到recv()返回空字节(代表对方关闭了连接的发送方向)。但客户端发送完HDF5数据后,未告知服务器“数据已发送完毕”,导致服务器一直等待更多数据,永远执行不到后续的回传逻辑。客户端卡在接收循环:
客户端发送完数据后立刻进入自身的recv()循环等待回传,但服务器还停留在接收阶段,根本没有发送任何数据,最终客户端也陷入阻塞。
修复方案
方案1:先传递数据长度(推荐,更健壮)
TCP没有天然的消息边界,可先发送数据的字节长度,让接收方明确需要接收的字节数,避免无限等待:
修正后的服务器代码
import socket import h5py import io import struct HOST = 'localhost' PORT = 3512 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen() conn, address = s.accept() # 先接收数据长度(4字节大端格式) len_data = conn.recv(4) if not len_data: conn.close() exit() file_len = struct.unpack('!I', len_data)[0] # 接收指定长度的文件数据 chunks = [] received = 0 while received < file_len: chunk = conn.recv(min(1024, file_len - received)) if not chunk: break chunks.append(chunk) received += len(chunk) # 读取HDF5文件并回传 with h5py.File(io.BytesIO(b''.join(chunks)), 'r') as f: print(f.keys()) img = f.id.get_file_image() # 先发送回传数据的长度 conn.send(struct.pack('!I', len(img))) # 循环发送确保全部数据发出 sent = 0 while sent < len(img): sent += conn.send(img[sent:]) conn.close()
修正后的客户端代码
import socket import h5py import io import struct HOST = 'localhost' PORT = 3512 sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sc.connect((HOST, PORT)) # 创建测试HDF5文件 h5_f = h5py.File("test_file.h5", 'a') grp = h5_f.create_group("Something") grp.create_dataset("test", shape=(50,50)) h5_f.flush() # 获取文件镜像并发送 img = h5_f.id.get_file_image() # 先发送数据长度 sc.send(struct.pack('!I', len(img))) # 循环发送全部数据 sent = 0 while sent < len(img): sent += sc.send(img[sent:]) # 接收服务器回传的数据 # 先接收长度 len_data = sc.recv(4) file_len = struct.unpack('!I', len_data)[0] chunks = [] received = 0 while received < file_len: chunk = sc.recv(min(1024, file_len - received)) if not chunk: break chunks.append(chunk) received += len(chunk) # 加载回传的HDF5文件 with h5py.File(io.BytesIO(b''.join(chunks)), 'r') as f: print(f.keys()) sc.close()
方案2:发送完数据后关闭写端(适合短连接场景)
如果是单次双向传输,客户端发送完数据后可关闭socket的写方向(保留读方向),让服务器知晓数据已发送完毕:
客户端修改点
在sc.send(img)之后添加:
# 关闭写端,告知服务器数据发送完毕 sc.shutdown(socket.SHUT_WR)
服务器修改点
发送完回传数据后,关闭连接的写端,让客户端知晓回传结束:
conn.send(img) # 关闭写端 conn.shutdown(socket.SHUT_WR)
注意:此方式仅适用于单次双向传输,后续socket无法再发送数据。
额外注意事项
socket.send()不一定会一次性发送全部数据,尤其是数据量较大时,必须通过循环确保所有字节都已发送。- 用
struct模块打包长度时,使用大端格式(!I)可保证跨平台兼容性。
内容的提问来源于stack exchange,提问作者Rama Vasudevan
相关产品推荐
相关产品推荐

