在流中重新组装Python对象的最安全方法是什么?

4
我目前正在两个运行中的程序之间通过sockets发送Python(3.8)pickle对象。我有一些字节缓冲区,我想在接收端将其重构为相应的对象。
据我所知,socket.recv方法不能保证捕获所有发送的字节,需要调用者再次调用socket.recv来接收剩余的数据。因此,在任何给定时间,我的缓冲区都可能包含部分数据包。
此外,由于我使用了线程,我在检查缓冲区之前可能会收到多个消息。
我的问题是: 考虑到我正在接收任意长度的字节流,其中可能包含少于或多于一个pickle对象,最好的重新组装方法是什么?是否有一个字符可用作终止符,保证不与pickle冲突?

3
pickle中,不存在可以保证不出现的字符或字符序列。在流协议中发送多个大小未知的数据块的通常方式是在每个数据块之前附加其长度;使用.recv()方法接收完整的长度指示,然后继续调用.recv()方法,直到接收到该数量的字节为止。这些长度本身可以是固定大小的,也可以使用一些终止字符。 - jasonharper
1个回答

3

有没有一个终止符我可以使用,它保证不会与pickle冲突?

很遗憾,没有。pickle以二进制形式打包数据,因此任何一系列字节都可能出现在pickled对象中。

重新组装它们的最佳方法是什么?

处理这种问题时最常见(也可能是最简单)的方法是发送一个固定大小的标头,指示即将接收到的数据的大小。

您可以使用 struct.pack() 创建一个8字节标头,其中包含pickled对象大小的二进制表示(作为8字节网络字节顺序的无符号整数),然后将其发送到实际数据之前。在接收端,您首先会收到8字节标头,然后解码它以知道发送的数据大小,并最终接收恰好该数量的字节。

这里是一个(简化的)示例:

  • Sender:

    class Example:
        pass
    
    data = pickle.dumps(Example())
    size = len(data)
    header = struct.pack("!Q", size)
    
    # open socket...
    
    sock.sendall(header)
    sock.sendall(data)
    
  • Receiver:

    class Example:
        pass
    
    def receive_exactly(sock, n):
        data = b''
    
        while n > 0:
            chunk = sock.recv(n)
            n -= len(chunk)
            data += chunk
    
        return data
    
    # open socket...
    
    header = receive_exactly(sock, 8)
    size = struct.unpack("!Q", header)[0]
    data = receive_exactly(sock, size)
    e = pickle.loads(data)
    
请注意,上述两个代码片段仅作为简单示例,当使用sendall()recv()时,您应进行适当的错误检查和处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接