2017-08-10 14 views
0

我陷入了一个非常奇怪的情况。我一直在玩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') 

我送一点二值图像内容比特(​​每个),直到一切都已经发送。

Server代码:

# 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

让我们来看看你发送了什么:
首先你发送一个用raw_size = str.encode('{}\n{}'.format(image.size[0], image.size[1]))创建的字符串。所以这个字符串是由编码(显然)图像的宽度和高度的字符组成的。然后立即开始发送构成实际图像的特点,所以它会是这个样子(在这个例子中,假设800×600图像):

800\n600Bytesofimage.... 

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

要点: 什么会导致第一个recv在高度值后停止? TCP不保证保留消息边界,以至于它正在这样做,你会变得非常幸运。 (字符串解码中的“ignore_errors”可能隐藏了字节被丢弃的事实?)在第一次接收中,您很可能会收到超过width\nheight的信息。或者,也许你收到的剩余字节形成有效的ASCII数字?假设你已经制作了b'800\n600',但是如果图像数据的前10个字节等于b'7777773322'而那些碰巧与第一个缓冲区捆绑在一起呢?那么你可能会产生宽度为800,高度为6007777773322.

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

这里就是我想要做的:
转换的图像大小为固定长度(比如4个字节)使用struct.pack的二进制整数。转换宽度和高度(可能可以使用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使用'!'字符在它们的格式字符串的开头。这确保了任何一方的系统在编码和解码二进制整数时都会使用相同的字节顺序,即使它们的本地字节顺序不同。

+0

如果第一个'conn.recv(8)'只接收到6个字节会怎么样?你还需要一个循环来获得第一个接收。 –

+0

是的,你说得对。我应该做到这一点。在实践中,我知道没有一个实现不会在单个数据包中传送8个字节的初始“发送”,而8个字节的相应“recv”不会获得全部8个字节。但是,如果这是一个从循环 - 发送几个文件,说 - 可能[并可能最终会*]成为一个问题。我会更新答案。 –

+0

+1,我同意你的看法,但是你所做的一切都可能会被复制并永久粘贴。这就是为什么尽可能编写最健壮的代码片段总是好的。 –

相关问题