0

有没有办法在 C++ 中创建包含预编码内部消息的协议缓冲区消息,而无需解析然后重新序列化内部消息?

为了澄清,请考虑以下消息定义:

message Inner {
    required int i = 1;
    // ... more fields ...
}

message Outer {
    repeated Inner inners = 1;
    // ... more fields ...
}

假设你有一个 10 字节数组的集合,每个数组都包含一个 Inner 的编码版本。您想创建一个包含 10 个内层的外层。您不想手动编码,因为 Outer 有其他字段,并且本身可能包含在其他消息中。有没有办法让协议缓冲区直接复制预编码的内部?

4

1 回答 1

1

没有干净的方法,但有一些 hacky 方法。一种是像这样定义第二条消息:

message RawOuter {
    repeated bytes inners = 1;
    // ... same fields as Outer ...
}

RawOuterOuter除了inners重复字段已从 type 更改为 typeInner之外,与 相同bytes。如果您inners使用 的编码实例进行填充Inner,然后对 进行序列化,您将获得与使用已解析的版本RawOuter构建一个完全相同的结果。Outer也就是说,嵌套消息的有线格式与bytes包含该嵌套消息的序列化的字段的有线格式相同。这是 protobuf 编码的可利用怪癖之一。

不过,这个 hack 有一些问题。特别是,如果您尝试构建一个嵌入在其他原型中的实例,它就不能很好地工作Outer,因为您可能不想维护每个包含消息的两个副本,一个 usingOuter和一个 using RawOuter

另一个更老套的选择是将编码的消息注入Outer实例的UnknownFieldSet.

Outer outer;
for (auto& inner: inners) {
  outer.mutable_unknown_fields()
      ->AddLengthDelimited(1, inner);
}

旨在存储解析时看到的与文件中UnknownFieldSet定义的任何已知字段编号不匹配的字段。.proto这个想法是,这允许您编写一个代理服务器,它只接收消息并将它们转发到另一个服务器,而无需在每次向协议中添加新字段时重新编译代理。在这里,我们通过在其中插入一个实际上对应于已知字段的值来滥用它,但实现不会注意到,因此它会很好地写出这些字段。

这种方法的主要问题是,如果其他人Outer同时检查您的实例,在他们看来,列表好像inners是空的,因为这些值实际上隐藏在其他地方。这是一个非常丑陋的黑客,以后可能会回来困扰你。如果您测量了性能差异并发现它很大,我只会推荐它。

另请注意,序列化代码总是最后写入未知字段,而已知字段按字段编号顺序写入。解析器应该接受任何顺序,但有时您会发现有人将未解析的数据用作哈希映射键或其他东西,如果重新排序字段,则会完全中断。

顺便说一句,您可以通过将字符串交换到位而不是复制来提高这两种方法的性能,即

raw_outer->add_inners()->swap(inner);

或者

outer->mutable_unknown_fields()->AddLengthDelimited(1)->swap(inner);
于 2013-08-29T20:59:27.343 回答