我正在尝试编写一个应用程序,该应用程序使用 Google 的协议缓冲区通过 TCP 连接反序列化数据(使用协议缓冲区从另一个应用程序发送)。问题是它看起来好像 Python 中的协议缓冲区只能反序列化字符串中的数据。由于 TCP 没有明确定义的消息边界,并且我尝试接收的消息之一具有重复字段,因此在最终传递要反序列化的字符串之前,我不知道要尝试和接收多少数据。
在 Python 中这样做有什么好的做法吗?
我正在尝试编写一个应用程序,该应用程序使用 Google 的协议缓冲区通过 TCP 连接反序列化数据(使用协议缓冲区从另一个应用程序发送)。问题是它看起来好像 Python 中的协议缓冲区只能反序列化字符串中的数据。由于 TCP 没有明确定义的消息边界,并且我尝试接收的消息之一具有重复字段,因此在最终传递要反序列化的字符串之前,我不知道要尝试和接收多少数据。
在 Python 中这样做有什么好的做法吗?
不要只是将序列化的数据写入套接字。首先发送一个包含序列化对象长度的固定大小字段。
发送方大致是:
socket.write(struct.pack("H", len(data)) #send a two-byte size field
socket.write(data)
并且recv'ing方面变成了这样:
dataToRead = struct.unpack("H", socket.read(2))[0]
data = socket.read(dataToRead)
这是套接字编程的常见设计模式。大多数设计都扩展了无线结构以包含类型字段,因此您的接收端变成了这样:
type = socket.read(1) # get the type of msg
dataToRead = struct.unpack("H", socket.read(2))[0] # get the len of the msg
data = socket.read(dataToRead) # read the msg
if TYPE_FOO == type:
handleFoo(data)
elif TYPE_BAR == type:
handleBar(data)
else:
raise UnknownTypeException(type)
您最终会得到如下所示的无线消息格式:
struct {
unsigned char type;
unsigned short length;
void *data;
}
这可以合理地针对未来的要求对有线协议进行验证。这是一个类型-长度-值协议,您会在网络协议中一次又一次地找到它。
为了扩展 JJ 的(完全正确的)答案,protobuf 库无法计算出消息的长度,或者计算出正在发送的 protobuf 对象的类型*。因此,向您发送数据的其他应用程序必须已经在执行类似的操作。
当我不得不这样做时,我实现了一个查找表:
messageLookup={0:foobar_pb2.MessageFoo,1:foobar_pb2.MessageBar,2:foobar_pb2.MessageBaz}
...并且基本上做了 JJ 所做的事情,但我也有一个辅助函数:
def parseMessage(self,msgType,stringMessage):
msgClass=messageLookup[msgType]
message=msgClass()
message.ParseFromString(stringMessage)
return message
...我调用它来将字符串转换为 protobuf 对象。
(*) 我认为可以通过将特定消息封装在容器消息中来解决这个问题
要考虑的另一个方面(尽管是更简单的情况)是您对单个消息使用单个 TCP 连接。在这种情况下,只要知道预期的消息是什么(或在运行时使用Union Types确定消息类型),就可以使用 TCP 连接打开作为“开始”分隔符,连接关闭事件为最后的分隔符。这样做的好处是您可以快速收到整个消息(而在其他情况下,TCP 流可能会保留一段时间,从而延迟整个消息的接收)。如果这样做,则不需要任何显式的带内成帧,因为 TCP 连接的生命周期本身就是一个帧。