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

What is the safest way to reassemble pickled Python objects in a stream?

我目前正在通过两个 运行 程序之间的套接字发送 pickled Python (3.8) 对象。我有一个字节缓冲区,我想在接收端将其重建为相应的对象。

据我了解,socket.recv 方法不能保证捕获所有发送的字节,调用者需要再次调用 socket.recv 以获取其余数据。因此,在任何给定时间,我的缓冲区都可能包含部分数据包。

此外,由于我使用线程,在检查缓冲区之前我可能会收到多条消息。

这是我的问题:

鉴于我正在接收任意长度的字节流,其中可能包含少于或多于一个 pickle 对象,重新组合它们的最佳方法是什么? 有没有我可以用作终止符的字符保证不会与 pickle?

冲突

Is there a character I can use as a terminator that is guaranteed to not conflict with pickle?

遗憾的是没有。 Pickle 以二进制形式打包数据,因此任何字节序列都可以出现在 pickled object.

what is the best way to reassemble them?

处理此类问题时最常见(也可能是最简单)的做法是发送一个 fixed-size header 指示要发送的数据大小收到。

您可以使用 struct.pack() 创建一个 8 字节 header 包含腌制大小的二进制表示(作为 8 字节 network-endian 无符号整数)object,并在实际数据之前发送。在接收端,您将首先收到 8 个字节 header,然后对其进行解码以了解发送的数据的大小,最后准确接收到该字节数。

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

  • 发件人:

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

    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() 时应进行适当的错误检查和处理。