Python中使用socket.recv()接收TCP/IP图像时出现损坏问题
你的核心问题在于TCP是无消息边界的字节流协议,recv()方法并不会一次性返回你请求的所有字节——它只会返回当前缓冲区中可用的数据,可能小于你指定的长度。而你直接用单次recv()来获取固定长度的头部(4字节大小、2字节计数器)和图像数据,很可能只拿到了部分数据,导致后续解析完全错乱,最终图像损坏。
makefile()方法之所以能正常工作,是因为它的read()方法内部会自动循环读取,直到凑够你指定的字节数,帮你处理了流的边界问题;而直接用recv()时,你需要自己实现这个逻辑。
修复方案:实现一个「固定长度接收」的辅助函数
我们可以写一个简单的函数,确保每次都能接收到指定长度的数据,而不是依赖单次recv()的返回结果:
def recv_all(sock, expected_length): """从socket接收指定长度的字节数据,直到全部接收完毕""" received_data = b'' while len(received_data) < expected_length: chunk = sock.recv(expected_length - len(received_data)) if not chunk: # 连接已关闭,返回已接收的部分(或抛出异常) return received_data received_data += chunk return received_data
然后修改你的服务端代码,把所有需要固定长度接收的地方替换成这个函数:
import socket import struct import io import cv2 import numpy as np def recv_all(sock, expected_length): received_data = b'' while len(received_data) < expected_length: chunk = sock.recv(expected_length - len(received_data)) if not chunk: return received_data received_data += chunk return received_data server_socket= socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_socket.bind(('192.168.1.106',9000)) server_socket.listen(1) print('Server is ready') (client_conn,client_addr)= server_socket.accept() image_stream= io.BytesIO() try: while True: # 接收4字节的图像大小 size_data = recv_all(client_conn, 4) if not size_data: break data_size=struct.unpack('<L', size_data)[0] # 接收2字节的计数器 counter_data = recv_all(client_conn, 2) if not counter_data: break counter=struct.unpack('<h', counter_data)[0] print('Image Count = ',counter) print('Image size = ',data_size) if not data_size: break # 接收完整的图像数据 image_data = recv_all(client_conn, data_size) image_stream.write(image_data) image= np.frombuffer(image_stream.getvalue(), dtype=np.uint8) img=cv2.imdecode(image, -1) cv2.imwrite((str(counter)+'.jpeg'),img) image_stream.seek(0) image_stream.truncate() finally: server_socket.close() print('Server Closed.')
关键细节解释
为什么单次recv不可靠?
TCP会把数据拆分成多个数据包传输,或者把多个小消息合并成一个数据包发送(粘包)。recv(n)最多返回n字节,但可能只返回当前到达的部分数据——比如你请求4字节,可能先收到2字节,剩下的2字节后续才到达。如果不循环接收,就会用不完整的头部数据去解析,导致后续所有数据的位置都错乱,图像自然损坏。sendall()和recv()的区别
sendall()确实会保证把所有数据发送出去(如果连接没断),但它只是负责把数据交给TCP栈,TCP会处理分片和重传;而recv()只负责从本地缓冲区拿当前可用的数据,不会自动等待后续数据,所以必须由你自己处理「凑够长度」的逻辑。关于
np.fromstring
注意np.fromstring()已经被弃用了,建议改用np.frombuffer(),我在修复代码里已经帮你替换了,避免后续版本的兼容性问题。
这样修改后,你就能确保每次都接收到完整的头部和图像数据,图像应该就能正常保存了。
内容的提问来源于stack exchange,提问作者Adarsh

