通过套接字将图像发送到外部服务器时数据丢失

时间:2017-08-10 17:38:58

标签: python sockets

我陷入了一种非常奇怪的境地。我一直在玩socketsPIL库,并准备将客户端捕获的图像发送到服务器。

如果我在本地计算机上测试它,一切都按预期进行。服务器将接收所有数据,最终可以制作图像。
但是,如果我想将图像发送到不在本地网络上的外部服务器,则还有一些尚未发送的字节。

客户代码:

# make a screenshot and store it as bytes
raw_image = ImageGrab.grab().tobytes()
print('Actual image size: {}'.format(len(raw_image)))

# send the image resolution to the server
raw_size = str.encode('{}\n{}'.format(image.size[0], image.size[1]))
sock.send(raw_size)


x = 512
y = 0
while y < len(raw_image):
    sock.send(raw_image[y:x])
    y = x
    x += 512

# tell the server that the client is done sending the data
sock.send(b'sent')

我一点一点地发送二进制图像内容(每个512 bytes),直到所有内容都被发送完毕。

服务器代码:

# receiving the image size for later usage
img_size = str(conn.recv(1024), 'utf-8', errors='ignore')
width = int(img_size.split('\n')[0])
height = int(img_size.split('\n')[1])

# receiving the binary data
raw_img = b''
while True:
    raw_prt = conn.recv(512)
    # "sent" will be sent by the client indicating that all data has been transferred
    if b'sent' in raw_prt:
        break
    raw_img += raw_prt

print('Received image size: {}'.format(len(raw_img)))


客户端输出:

Actual image size: 6220800

服务器输出:

Received image size: 6220751

如您所见,剩余49 bytes尚未收到。丢失的字节数从30200不等。这对于从二进制文件创建图像至关重要。我做错了什么?

谢谢。

1 个答案:

答案 0 :(得分:1)

让我们看看你发送的确切内容:
首先,您要发送使用raw_size = str.encode('{}\n{}'.format(image.size[0], image.size[1]))创建的字符串。因此,该字符串由编码(显然)图像的宽度和高度的字符组成。然后,您立即开始发送构成实际图像的字符,因此它看起来像这样(对于此示例,假设800x600图像):

800\n600Bytesofimage....

现在让我们来看看你接收的内容:
您的第一个recv将拉出第一个(最多)1024个字符的数据。然后,您将在第一个\n拆分一次并将第一个块转换为整数(给您800)。而第二个块为整数。但...

关键点: 什么会导致第一个recv在高度值后停止? TCP不保证保留消息边界所以在这样做的范围内,你会非常幸运。 (可能&#34; ignore_errors&#34;对你的字符串解码隐藏了丢弃字节的事实?)你很可能在第一次收到的字符超过width\nheight接收。或者您接收的剩余字节可能形成有效的ASCII数字?所以,让我们说你已经生成了b'800\n600'但是如果你的图像数据的前10个字节等于b'7777773322'并且那些碰巧与第一个缓冲区捆绑在一起会怎样?那么你可能会产生800的宽度和6007777773322的高度。

最后,您要在数据中查找字节序列b'sent'作为退出接收的信号。但是,再一次,你隐含地假设该字符串将在它发送时自动显示为完整的缓冲区内容。更有可能的是,它与之前的图像数据相结合,因此您将丢弃该缓冲区的开头,因为它恰好包含字符串b'sent'。这肯定会解释短图像数据。 (另外,如果图像中的像素值恰好包含与ASCII值sent匹配的二进制序列,该怎么办?)

这就是我要做的事情:
使用struct.pack将图像大小转换为固定长度(例如4字节)二进制整数。转换宽度和高度(可能使用2字节整数)。发送这些值(组合长度恰好为8)。然后发送图像数据。

在接收方,接收前8个字节,struct.unpack它们以获得原始值。现在,接收方确切地知道预期有多少额外字节,并且您不需要解析字符串。

IOW ...客户(编辑):

# Encode image size, width and height into bytes
buff = struct.pack("!IHH", len(raw_image), image.size[0], image.size[1])
sock.sendall(buff)          # (byte buffer with length of 8)
sock.sendall(raw_image)     # Send entire image

服务器(已编辑):

def recv_exactly(conn, n):
    recv_buf = b''
    remaining_bytes = n
    while remaining_bytes > 0:
        count = remaining_bytes if remaining_bytes < 4096 else 4096
        buff = conn.recv(count)
        if not buff:
            raise Exception("Connection closed in middle of expected buffer")
        recv_buf += buff
        remaining_bytes -= len(buff)
    return recv_buf

buff = recv_exactly(conn, 8)
image_size, width, height = struct.unpack("!IHH", buff)
raw_img = recv_exactly(conn, image_size)

请注意,packunpack在其格式字符串的开头使用'!'字符。这确保了任何一方的系统在编码和解码二进制整数时都会使用相同的字节顺序,即使它们的本机字节顺序不同。