protobuf如何判断值是属于可选字段还是另一个对象?

时间:2017-01-12 13:06:45

标签: linux object protocol-buffers encode delimiter

例如,如果我定义这样的照片:

$cat 30.proto
message hello
{
    required int32 f1=1;
    required int32 f2=2;
    optional int32 f3=3;
}

如果protobuf可以处理这样的事情,我会加倍:

  1. 我声明了3个对象,每个对象都没有f3字段。

  2. 写入输出

  3. 然后,在读者方面,读者如何知道这6个值应属于3个对象(每2个字段),或属于2个对象(每3个字段)?

  4. 换句话说,“require”/“optional”如何反映在编码字节内?如果没有反映在字节流中,那么protobuf如何确定新偏移的开始?我们知道protobuf没有“分隔符”位。

    我对此进行了简单的快速测试:

    $cat 30.cpp
    #include "30.pb.h"
    #include<fstream>
    using namespace std;
    int main()
    {
        fstream f("./log30.data",ios::binary|ios::out);
        hello p1,p2,p3,p4,p5;
        p1.set_f1(1);
        p1.set_f2(2);
        p2.set_f1(3);
        p2.set_f2(4);
        p3.set_f1(5);
        p3.set_f2(6);
        p1.SerializeToOstream(&f);
        p2.SerializeToOstream(&f);
        p3.SerializeToOstream(&f);
    
        p4.set_f1(7);
        p4.set_f2(8);
        p4.set_f3(9);
        p5.set_f1(0xa);
        p5.set_f2(0xb);
        p5.set_f3(0xc);
        p4.SerializeToOstream(&f);
        p5.SerializeToOstream(&f);
        return 0;
    }
    
    $g++ 30.cpp 30.pb.cc -lprotobuf && ./a.out && xxd log30.data
    00000000: 0801 1002 0803 1004 0805 1006 0807 1008  ................
    00000010: 1809 080a 100b 180c                      ........
    

    我猜测字节流总是以最小的标签号开头,并且在转储字节流时增加:当满足较小的标签号时,它认为这是新对象的开始。只是我的谦虚猜测。

    需要你的解释!

2 个答案:

答案 0 :(得分:1)

  

(3)然后,在读者方面,读者如何知道这6个值   应属于3个对象(每2个字段),或属于2个对象(每个对象)   3个领域)?

     

换句话说,内部如何反映“要求”/“可选”   编码字节?如果没有反映在字节流中,那怎么办   protobuf确定新偏移的开始?我们知道protobuf没有   有“分隔符”位。

Protobuf没有。程序员可以在将消息提供给protobuf之前拆分消息。

例如,运行此程序:

#include "30.pb.h"
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
    fstream f("./log30.data",ios::binary|ios::out);
    hello p1,p2,p3,p4,p5;
    p1.set_f1(1);
    p1.set_f2(2);
    p2.set_f1(3);
    p2.set_f2(4);
    p3.set_f1(5);
    p3.set_f2(6);
    p1.SerializeToOstream(&f);
    p2.SerializeToOstream(&f);
    p3.SerializeToOstream(&f);

    p4.set_f1(7);
    p4.set_f2(8);
    p4.set_f3(9);
    p5.set_f1(0xa);
    p5.set_f2(0xb);
    p5.set_f3(0xc);
    p4.SerializeToOstream(&f);
    p5.SerializeToOstream(&f);
    f.close();
    f.open("./log30.data", ios::binary|ios::in);

    hello hin;
    hin.ParseFromIstream(&f);

    cout << "f1: " << hin.f1() << ", f2: " << hin.f2() << ", f3: " << hin.f3() << "\n";
    return 0;
}

您应该只看到上一个序列化hello对象的值,因为protobuf读取整个流并用较新的值覆盖旧值。

答案 1 :(得分:1)

形成documentation

  

如您所知,协议缓冲区消息是一系列键值对。消息的二进制版本只使用字段的数字作为密钥 - 每个字段的名称和声明的类型只能通过引用消息类型的定义(即.proto文件)在解码端确定。

     

对消息进行编码时,键和值将连接成字节流。在解码消息时,解析器需要能够跳过它无法识别的字段。这样,可以将新字段添加到消息中,而不会破坏不了解它们的旧程序。为此,有线格式消息中每对的“键”实际上是两个值 - 来自.proto文件的字段编号,以及提供足够信息以查找以下值的长度的线型。 / p>      

...

     

如果proto2消息定义具有重复元素(没有[packed = true]选项),则编码消息具有零个或多个具有相同标记号的键值对。

因此无法将可选元素放入输出流中。虽然必须包括在内。序列化和反序列化都必须知道模式(与Avro where schema must be embedded with data不同),因此当解析器检查所有必需字段是否具有值时,反序列化后会发生必需/可选字段的验证。