使用 Header 和重复字段反序列化流协议缓冲区消息

Deserializing a Streamed Protocol Buffer Message With Header and Repeated fields

我正在反序列化已使用协议缓冲区(和 NanoPB)在 C 中序列化的日志文件。

日志文件有一个简短的 header 组成:实体、版本和标识符。在 header 之后,数据流应该是连续的,它应该记录来自传感器的字段而不是 header 值(这应该只发生一次并且在开头)。相同的 .proto 文件用于序列化文件。 header 和流数据没有单独的 .proto 文件。

实施后,我认为它应该如下所示:

firmware "1.0.0"
GUID "1231214211321" (example)
Timestamp 123123
Sens1 2343
Sens2 13123
Sens3 13443
Sens4 1231
Sens5 190
Timestamp 123124
Sens1 2345
Sens2 2312
...

当我在 C 中实现序列化时,我发布了 来弄清楚最初如何构建 .proto 文件。最后我使用了类似的方法但没有包括:[( nanopb).max_count = 1];

最后我在 Python 中选择了以下 .proto(传感器可以超过 5 个):

syntax = "proto3";

import "timestamp.proto";

 message SessionLogs {
int32 Entity = 1;      
string Version = 2;  
string GUID = 3;     
repeated SessionLogsDetail LogDetail = 4;
}
message SessionLogsDetail 
{    
int32 DataTimestamp = 1;        // internal counter to identify the order of session logs
// Sensor data, there can be X amount of sensors.
int32 sens1 = 2;
int32 sens2= 3;
int32 sens3= 4;
int32 sens4= 5;
}

此时,我可以在使用我的设备登录时序列化一条消息,根据文件大小,日志似乎可以正常工作,但我无法在 Python 上离线反序列化它检查我的实施是否正确。而且我不能在 C 中执行此操作,因为它是一个嵌入式应用程序,我想使用 Python.

离线执行 post-processing

另外,我检查了 this online protobuf deserializer 我可以在哪里传递序列化文件并在不需要 .proto 文件的情况下将其反序列化。在其中我可以看到 header 值(字段 3 为空,因此看不到)和记录的信息。所以这让我认为序列化是正确的,但我在 Python.

上错误地反序列化了它

这是我当前用于反序列化 Python 中消息的代码:

import PSessionLogs_pb2

with open('$PROTOBUF_LOG_FILENAME$', 'rb') as f:
read_metric =  PSessionLogs_pb2.PSessionLogs()
read_metric.ParseFromString(f.read())

除此之外,我还使用 protoc 生成了与 .proto 文件等效的 .py 文件以进行脱机反序列化。

看起来您已经序列化了一个 header,然后立即序列化了一些其他数据,这意味着:不是序列化一个 具有 一些 SessionLogsDetail 记录的 SessionLogs,您已经序列化了一个 SessionLogs,然后(单独)序列化了一个 SessionLogsDetail - 听起来对吗?如果是这样:是的,那将无法正常工作;有 种方法可以做你想做的事,但它并不像一个接一个地序列化那么简单,因为根 protobuf object 永不终止;所以实际发生的是它用后面的字段按数字覆盖根object。

有两种方法可以解决这个问题,具体取决于数据量。如果大小(包括所有详细信息行)很小,您可以更改代码,使其成为真正的 parent / child 关系,即行都在 parent。在写入数据时,这 并不 意味着您需要在开始写入之前拥有所有行 - 有一些方法可以附加 child 行以便您发送数据当它可用时;然而,在反序列化时,它会想要一次性加载所有内容,所以这种方法只有在你同意的情况下才有用,即你没有淫秽open-ended 行数。

如果您有大量行,则基本上需要添加自己的框架。这通常是通过在每个有效负载之间添加一个 length-prefix 来完成的,这样您基本上可以一次读取一条消息。一些库为此包含辅助方法;例如,在 java API 中,这是 parseDelimitedFromwriteDelimitedTo。然而,我的理解是python API 目前不支持这个实用程序,所以你需要自己做框架:(


总而言之,您目前拥有:

{header - SessionLogs}
{row 0 - SessionLogsDetail}
{row 1 - SessionLogsDetail}

选项 1 是:

{header - SessionLogs
  {row 0 - SessionLogsDetail}
  {row 1 - SessionLogsDetail}
}

选项 2 是:

{length prefix of header}
{header - SessionLogs}
{length prefix of row0}
{row 0 - SessionLogsDetail}
{length prefix of row1}
{row 1 - SessionLogsDetail}

(其中长度前缀是一些简单的东西,比如原始 varint,或者只是一个约定字节序的 4 字节整数)