【问题标题】:How does protobuf judge if a value belongs to an optional field, or another object?protobuf 如何判断一个值是属于一个可选字段,还是属于另一个对象?
【发布时间】:2017-01-12 21:26:31
【问题描述】:

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

$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个字段)?

换句话说,“需要”/“可选”如何反映在编码字节中?如果不反映在字节流中,那么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                      ........

我只是猜测字节流是否总是以最小的标记号开始,并随着它转储字节流而增加:当遇到较小的标记号时,它认为这是一个新对象的开始。只是我的拙见。

需要你的解释!

【问题讨论】:

    标签: linux object protocol-buffers encode delimiter


    【解决方案1】:

    (3) 那么,在阅读器方面,阅读器如何知道这 6 个值 应该属于 3 个对象(每个 2 个字段),或属于 2 个对象(每个 3 个字段)?

    换句话说,里面的“require”/“optional”是怎么体现的 编码字节?如果没有体现在字节流中,那怎么办 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 读取整个流并用新值覆盖旧值。

    【讨论】:

      【解决方案2】:

      形成documentation

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

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

      ...

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

      因此无法将可选元素放入输出流中。虽然必须包括必需的。 Schema 必须对序列化和反序列化都是已知的(与 Avro where schema must be embedded with data 相比),因此当解析器检查所有必填字段是否都有值时,必填/可选字段的验证会在反序列化之后进行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-19
        • 2010-10-28
        • 1970-01-01
        • 2012-12-03
        • 2013-03-11
        • 1970-01-01
        • 1970-01-01
        • 2015-04-07
        相关资源
        最近更新 更多