Proto3语法详解02

目录

1.默认值

2.更新消息

2.1更新规则

2.2保留字段reserved

2.2.1创建通讯录3.0版本---验证错误删除字段造成的数据损坏

 2.3未知字段

2.3.1未知字段从哪获取

3.3.2升级通讯录3.1版本--验证未知字段

2.4前后兼容性

3.选项option

3.1选项分类

3.2常用选项列举


1.默认值

反序列化消息时,如果被反序列化的二进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同: 
●对于字符串,默认值为空字符串。
●对于字节,默认值为空字节。
●对于布尔值,默认值为false。
对于数值类型,默认值为0。
对于枚举,默认值是第一个定义的枚举值,必须为0。
●对于消息字段,未设置该字段。它的取值是依赖于语言。
●对于设置了repeated的字段的默认值是空的(通常是相应语言的一-个空列表)。
对于消息字段、oneof字段 和any字段,C++和Java语言中都有has_方法来检测当前字段
是否被设置。

2.更新消息

2.1更新规则

如果现有的消息类型已经不再满足我们的需求,例如需要扩展一个字段,在不破坏任何现有代码的情况下更新消息类型非常简单。遵循如下规则即可:
● 禁止修改任何已有字段的字段编号。
●若是移除老字段,要保证不再使用移除字段的字段编号。正确的做法是保留字段编号
(reserved),以确保该编号将不能被重复使用。不建议直接删除或注释掉字段。
●int32, uint32, int64, uint64和bool是完全兼容的。可以从这些类型中的一个改为另一个,
而不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采用与C++一致的处理方案;
(例如,若将64位整数当做32位进行读取,它将被截断为32位)
● sint32和sint64相互兼容但不与其他的整型兼容。
●string和bytes在合法UTF-8字节前提下也是兼容的。
●bytes包含消息编码版本的情况下,嵌套消息与bytes也是兼容的。

●enum与int32, uint32, int64 和uint64兼容(注 意若值不匹配会被截断)。但要注意当反序
列化消息时会根据语言采用不同的处理方案:例如,未识别的proto3枚举类型会被保存在消息
中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。
●oneof:
        ●将一个单独的值更改为新oneof类型成员之一 是安全和二进制兼容的。
        ●若确定没有代码一次性设置多个值那么将多个字段移入一个新oneof类型也是可行的。
        ●将任何字段移入已存在的oneof类型是不安全的。

2.2保留字段reserved

如果通过删除或注释掉字段来更新消息类型,未来的用户在添加新字段时,有可能会使用以前已经
存在,但已经被删除或注释掉的字段编号。将来使用该.proto的旧版本时的程序会引发很多问题:数
据损坏、隐私错误等等。
确保不会发生这种情况的一种方法是:使用reserved将指定字段的编号或名称设置为保留项。当
我们再使用这些编号或名称时,protocol buffer的编译器将会警告这些编号或名称不可用。

举个例子:

message Message {
    // 设置保留项
    reserved 100, 101, 200 to 299;
    reserved "field3", "field4";
    // 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
    // reserved 102, "field5";
    // 设置保留项之后,下⾯代码会告警
    int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
    int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
    int32 field3 = 102; //告警:Field name 'field3' is reserved
    int32 field4 = 103; //告警:Field name 'field4' is reserved
}

2.2.1创建通讯录3.0版本---验证错误删除字段造成的数据损坏

现模拟有两个服务,他们各自使用一份通讯录.proto文件,内容约定好了是一模一样的。
服务1 (service) : 负责序列化通讯录对象,并写入文件中。
服务2 (client) :负责读取文件中的数据,解析并打印出来。
一段时间后,service 更新了自己的.proto文件,更新内容为:删除了某个字段,并新增了一个字段,
新增的字段使用了被删除字段的字段编号。并将新的序列化对象写进了文件。
但client并没有更新自己的.proto文件。根据结论,可能会出现数据损坏的现象,接下来就让我们来
验证下这个结论。

新建两个目录: service、 client。 分别存放两个服务的代码。
service目录下新增contacts. proto (通讯录 3.0)

syntax = "proto3";
package s_contacts;
// 联系⼈
message PeopleInfo {
    string name = 1; // 姓名
    int32 age = 2; // 年龄
    message Phone {
    string number = 1; // 电话号码
}
    repeated Phone phone = 3; // 电话
}
// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

client目录下新增contacts.proto (通讯录 3.0)

syntax = "proto3";
package c_contacts;
// 联系⼈
message PeopleInfo {
    string name = 1; // 姓名
    int32 age = 2; // 年龄
    message Phone {
    string number = 1; // 电话号码
}
    repeated Phone phone = 3; // 电话
}
// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

分别对两个文件进行编译,可自行操作。
继续对service目录下新增service.cc (通讯录3.0) ,负责向文件中写通讯录消息,内容如下:

#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;
using namespace s_contacts;
void AddPeopleInfo(PeopleInfo *people)
{
    cout << "-------------新增联系⼈-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);
    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');
    for (int i = 1;; ++i)
    {
        cout << "请输入联系人电话" << i << "(输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty())
            break;
        PeopleInfo_Phone *phone = people->add_phone();
        phone->set_number(number);
    }
    cout << "-------------添加联系人成功-------------" << endl;
}
int main()
{
    Contacts contacts;
    // 先读取已经存在的contacts:
    fstream input("../contacts.bin", ios::in | ios ::binary);
    if (!input)
    {
        cout << "file not exist,create new file" << endl;
    }
    else if (!contacts.ParseFromIstream(&input))
    {
        cerr << "Parse failed!" << endl;
        input.close();
        return -1;
    }
    // 向通讯录添加一个联系人:
    AddPeopleInfo(contacts.add_contacts());

    // 将通讯录写入到本地文件中:
    fstream output("../contacts.bin", ios::out | ios ::binary | ios ::trunc);
    if (!contacts.SerializePartialToOstream(&output))
    {
        cerr << "write failed !" << endl;
        input.close();
        output.close();
        return -1;
    }
    cout << "write sucess !" << endl;
    input.close();
    output.close();
    return 0;
}

service目录下新增makefile

server:server.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:
	rm server

client目录下新增client.cc (通讯录3.0) ,负责向读出文件中的通讯录消息,内容如下:

#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;
using namespace c_contacts;

void PrintContacts(const Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
        const PeopleInfo people = contacts.contacts(i);
        cout << "------------联系⼈" << i + 1 << "------------" << endl;
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        int j = 1;
        for (auto &phone : people.phone())
        {
            cout << "电话" << j++ << ": " << phone.number();
        }
    }
}
int main()
{
    Contacts contacts;
    fstream input("../contacts.bin", ios::in | ios ::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "Parse failed!" << endl;
        input.close();
        return -1;
    }
    // 打印contacts
    PrintContacts(contacts);
    input.close();
    return 0;
}

client目录下新增makefile 

client:client.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:
	rm client

代码编写完成后,进行一次读写

确认无误后,对service目录下的contacts.proto文件进行更新:删除age字段,新增birthday字
段,新增的字段使用被删除字段的字段编号。
更新后的contacts.proto (通讯录3.0)内容如下:

syntax = "proto3";
package s_contacts;
// 联系⼈
message PeopleInfo {
    string name = 1; // 姓名
    // 删除年龄字段
    // int32 age = 2; // 年龄
    int32 birthday = 2; // ⽣⽇
message Phone {
    string number = 1; // 电话号码
}
    repeated Phone phone = 3; // 电话
}
// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

编译文件.proto后,还需要更新一下对应的service.cc (通讯录3.0) :

#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;
using namespace s_contacts;
void AddPeopleInfo(PeopleInfo *people)
{
    cout << "-------------新增联系⼈-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);
    // cout << "请输入联系人年龄:";
    // int age;
    // cin >> age;
    // people->set_age(age);
    // cin.ignore(256, '\n');
    cout << "请输入联系人生日: ";
    int birthday;
    cin >> birthday;
    people->set_birthday(birthday);
    for (int i = 1;; ++i)
    {
        cout << "请输入联系人电话" << i << "(输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty())
            break;
        PeopleInfo_Phone *phone = people->add_phone();
        phone->set_number(number);
    }
    cout << "-------------添加联系人成功-------------" << endl;
}
int main()
{
    Contacts contacts;
    // 先读取已经存在的contacts:
    fstream input("../contacts.bin", ios::in | ios ::binary);
    if (!input)
    {
        cout << "file not exist,create new file" << endl;
    }
    else if (!contacts.ParseFromIstream(&input))
    {
        cerr << "Parse failed!" << endl;
        input.close();
        return -1;
    }
    // 向通讯录添加一个联系人:
    AddPeopleInfo(contacts.add_contacts());

    // 将通讯录写入到本地文件中:
    fstream output("../contacts.bin", ios::out | ios ::binary | ios ::trunc);
    if (!contacts.SerializePartialToOstream(&output))
    {
        cerr << "write failed !" << endl;
        input.close();
        output.close();
        return -1;
    }
    cout << "write sucess !" << endl;
    input.close();
    output.close();
    return 0;
}

我们对client相关的代码保持原样,不进行更新。
再进行一次读写

这时问题便出现了,我们发现输入的生日,在反序列化时,被设置到了使用了相同字段编号的年龄
上! !所以得出结论:若是移除老字段,要保证不再使用移除字段的字段编号,不建议直接删除或注
释掉字段。
那么正确的做法是保留字段编号(reserved) , 以确保该编号将不能被重复使用。

正确service目录下的contacts.proto写法如下(终版通讯录3.0)。

syntax = "proto3";
package s_contacts;
// 联系⼈
message PeopleInfo {
    reserved 2;
    string name = 1; // 姓名
    // int32 age = 2; // 年龄
    int32 birthday = 4;
    message Phone {
    string number = 1; // 电话号码
}
    repeated Phone phone = 3; // 电话
}
// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

编译.proto文件后,还需要重新编译下service.cc,让service程序保持使用新生成的pb C++文件。

根据实验结果,发现‘王五’ 的年龄为0,这是由于新增时未设置年龄,通过client程序反序列化
时,给年龄字段设置了默认值0。这个结果显然是我们想看到的。
还要解释一下‘ 李四’ 的年龄依旧使用了 之前设置的生日字段‘1221’ ,这是因为在新增‘李四’
的时候,生日字段的字段编号依旧为2,并且已经被序列化到文件中了。最后再读取的时候,字段编号依旧为2。
还要再说一下的是:因为使用了reserved 关键字,ProtoBuf在编译阶段就拒绝了我们使用已经保留
的字段编号。到此实验结束,也印证了我们的结论。

根据以上的例子,有的同学可能还有一-个疑问:如果使用了reserved 2了,那么service给‘王五’设置的生日.'0120’ , client就没法读到了吗?答案是可以的。继续学习下面的未知字段即可揭晓

 2.3未知字段

在通讯录3.0版本中,我们向service目录下的contacts.proto新增了‘生日’ 字段,但对于client相
关的代码并没有任何改动。验证后发现新代码序列化的消息(service) 也可以被旧代码(client) 解
析。并且这里要说的是,新增的‘生日’ 字段在旧程序(client) 中其实并没有丢失,而是会作为旧程
序的未知字段。
●未知字段:解析结构良好的protocol buffer已序列化数据中的未识别字段的表示方式。例如,当
旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。
本来,proto3 在解析消息时总是会丢弃未知字段,但在3.5版本中重新引入了对未知字段的保留机
制。所以在3.5或更高版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果
中。

2.3.1未知字段从哪获取

了解相关类关系图

MessageLite类介绍(了解)
MessageLite从名字看是轻量级的message,仅仅提供序列化、反序列化功能。
●类定义在google提供的message_lite.h中。
Message类介绍(了解)
●我们自定义的message类,都是继承自Message。
●Message最重要的两个接口GetDescriptor/GetReflection, 可以获取该类型对应的Descriptor对象
指针和Reflection对象指针。
●类定义在google提供的message.h中。

//google::protobuf::Message 部分代码展⽰
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;

Descriptor类介绍(了解)
●Descriptor: 是对message类型定义的描述,包括message的名字、 所有字段的描述、原始的
proto文件内容等。
●类定义在google提供的descriptor.h中。

// 部分代码展⽰
class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
    string& name () const
    int field_count() const;
    const FieldDescriptor* field(int index) const;
    const FieldDescriptor* FindFieldByNumber(int number) const;
    const FieldDescriptor* FindFieldByName(const std::string& name) const;
    const FieldDescriptor* FindFieldByLowercaseName(
    const std::string& lowercase_name) const;
    const FieldDescriptor* FindFieldByCamelcaseName(
    const std::string& camelcase_name) const;
    int enum_type_count() const;
    const EnumDescriptor* enum_type(int index) const;
    const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;
    const EnumValueDescriptor* FindEnumValueByName(const std::string& name) const;
}

Reflection类介绍(了解)

●提供方法来动态访问/修改message中的字段, 对每种类型,Reflection都提供 了一个单独的接口用于读写字段对应的值。
。针对所有不同的field类型FieldDescriptor: :TYPE_ * ,需要使用不同的Get*() /Set*() /Add*() 接口;
。repeated类型需要使用GetRepeated* () /SetRepeated*() 接口,不可以和非repeated
。message对象只可以被由它自身的reflection (message . GetReflection()) 来操
●类中还包含了访问/修改未知字段的方法。
●类定义在google提供的message.h中。

// 部分代码展⽰
class PROTOBUF_EXPORT Reflection final
{
    const UnknownFieldSet &GetUnknownFields(const Message &message) const;
    UnknownFieldSet *MutableUnknownFields(Message *message) const;
    bool HasField(const Message &message, const FieldDescriptor *field) const;
    int FieldSize(const Message &message, const FieldDescriptor *field) const;
    void ClearField(Message *message, const FieldDescriptor *field) const;
    bool HasOneof(const Message &message,
                  const OneofDescriptor *oneof_descriptor) const;
    void ClearOneof(Message *message,
                    const OneofDescriptor *oneof_descriptor) const;
    const FieldDescriptor *GetOneofFieldDescriptor(
        const Message &message, const OneofDescriptor *oneof_descriptor) const;
    // Singular field getters ------------------------------------------
    // These get the value of a non-repeated field. They return the default
    // value for fields that aren't set.
    int32_t GetInt32(const Message &message, const FieldDescriptor *field) const;
    int64_t GetInt64(const Message &message, const FieldDescriptor *field) const;
    uint32_t GetUInt32(const Message &message,
                       const FieldDescriptor *field) const;
    uint64_t GetUInt64(const Message &message,
                       const FieldDescriptor *field) const;
    float GetFloat(const Message &message, const FieldDescriptor *field) const;
    double GetDouble(const Message &message, const FieldDescriptor *field) const;
    bool GetBool(const Message &message, const FieldDescriptor *field) const;
    std::string GetString(const Message &message,
                          const FieldDescriptor *field) const;
    const EnumValueDescriptor *GetEnum(const Message &message,
                                       const FieldDescriptor *field) const;
    int GetEnumValue(const Message &message, const FieldDescriptor *field) const;
    const Message &GetMessage(const Message &message,
                              const FieldDescriptor *field,
                              MessageFactory *factory = nullptr) const;
    // Singular field mutators -----------------------------------------
    // These mutate the value of a non-repeated field.
    void SetInt32(Message *message, const FieldDescriptor *field,
                  int32_t value) const;
    void SetInt64(Message *message, const FieldDescriptor *field,
                  int64_t value) const;
    void SetUInt32(Message *message, const FieldDescriptor *field,
                   uint32_t value) const;
    void SetUInt64(Message *message, const FieldDescriptor *field,
                   uint64_t value) const;
    void SetFloat(Message *message, const FieldDescriptor *field,
                  float value) const;
    void SetDouble(Message *message, const FieldDescriptor *field,
                   double value) const;
    void SetBool(Message *message, const FieldDescriptor *field,
                 bool value) const;
    void SetString(Message *message, const FieldDescriptor *field,
                   std::string value) const;
    void SetEnum(Message *message, const FieldDescriptor *field,
                 const EnumValueDescriptor *value) const;
    void SetEnumValue(Message *message, const FieldDescriptor *field,
                      int value) const;
    Message *MutableMessage(Message *message, const FieldDescriptor *field,
                            MessageFactory *factory = nullptr) const;
    PROTOBUF_NODISCARD Message *ReleaseMessage(
        Message *message, const FieldDescriptor *field,
        MessageFactory *factory = nullptr) const;
    // Repeated field getters ------------------------------------------
    // These get the value of one element of a repeated field.
    int32_t GetRepeatedInt32(const Message &message, const FieldDescriptor *field,
                             int index) const;
    int64_t GetRepeatedInt64(const Message &message, const FieldDescriptor *field,
                             int index) const;
    uint32_t GetRepeatedUInt32(const Message &message,
                               const FieldDescriptor *field, int index) const;
    uint64_t GetRepeatedUInt64(const Message &message,
                               const FieldDescriptor *field, int index) const;
    float GetRepeatedFloat(const Message &message, const FieldDescriptor *field,
                           int index) const;
    double GetRepeatedDouble(const Message &message, const FieldDescriptor *field,
                             int index) const;
    bool GetRepeatedBool(const Message &message, const FieldDescriptor *field,
                         int index) const;
    std::string GetRepeatedString(const Message &message,
                                  const FieldDescriptor *field, int index) const;
    const EnumValueDescriptor *GetRepeatedEnum(const Message &message,
                                               const FieldDescriptor *field,
                                               int index) const;
    int GetRepeatedEnumValue(const Message &message, const FieldDescriptor *field,
                             int index) const;
    const Message &GetRepeatedMessage(const Message &message,
                                      const FieldDescriptor *field,
                                      int index) const;
    const std::string &GetRepeatedStringReference(const Message &message,
                                                  const FieldDescriptor *field,
                                                  int index,
                                                  std::string *scratch) const;
    // Repeated field mutators -----------------------------------------
    // These mutate the value of one element of a repeated field.
    void SetRepeatedInt32(Message *message, const FieldDescriptor *field,
                          int index, int32_t value) const;
    void SetRepeatedInt64(Message *message, const FieldDescriptor *field,
                          int index, int64_t value) const;
    void SetRepeatedUInt32(Message *message, const FieldDescriptor *field,
                           int index, uint32_t value) const;
    void SetRepeatedUInt64(Message *message, const FieldDescriptor *field,
                           int index, uint64_t value) const;
    void SetRepeatedFloat(Message *message, const FieldDescriptor *field,
                          int index, float value) const;
    void SetRepeatedDouble(Message *message, const FieldDescriptor *field,
                           int index, double value) const;
    void SetRepeatedBool(Message *message, const FieldDescriptor *field,
                         int index, bool value) const;
    void SetRepeatedString(Message *message, const FieldDescriptor *field,
                           int index, std::string value) const;
    void SetRepeatedEnum(Message *message, const FieldDescriptor *field,
                         int index, const EnumValueDescriptor *value) const;
    void SetRepeatedEnumValue(Message *message, const FieldDescriptor *field,
                              int index, int value) const;
    Message *MutableRepeatedMessage(Message *message,
                                    const FieldDescriptor *field,
                                    int index) const;
    // Repeated field adders -------------------------------------------
    // These add an element to a repeated field.
    void AddInt32(Message *message, const FieldDescriptor *field,
                  int32_t value) const;
    void AddInt64(Message *message, const FieldDescriptor *field,
                  int64_t value) const;
    void AddUInt32(Message *message, const FieldDescriptor *field,
                   uint32_t value) const;
    void AddUInt64(Message *message, const FieldDescriptor *field,
                   uint64_t value) const;
    void AddFloat(Message *message, const FieldDescriptor *field,
                  float value) const;
    void AddDouble(Message *message, const FieldDescriptor *field,
                   double value) const;
    void AddBool(Message *message, const FieldDescriptor *field,
                 bool value) const;
    void AddString(Message *message, const FieldDescriptor *field,
                   std::string value) const;
    void AddEnum(Message *message, const FieldDescriptor *field,
                 const EnumValueDescriptor *value) const;
    void AddEnumValue(Message *message, const FieldDescriptor *field,
                      int value) const;
    Message *AddMessage(Message *message, const FieldDescriptor *field,
                        MessageFactory *factory = nullptr) const;
    const FieldDescriptor *FindKnownExtensionByName(
        const std::string &name) const;
    const FieldDescriptor *FindKnownExtensionByNumber(int number) const;
    bool SupportsUnknownEnumValues() const;
};

UnknownFieldSet类介绍(重要)
●UnknownFieldSet 包含在分析消息时遇到但未由其类型定 义的所有字段。
●若要将 UnknownFieldSet附加到任何消息,请调用Reflection::GetUnknownFields()。
●类定义在unknown_ field_ set.h 中。

class PROTOBUF_EXPORT UnknownFieldSet
{
    inline void Clear();
    void ClearAndFreeMemory();
    inline bool empty() const;
    inline int field_count() const;
    inline const UnknownField &field(int index) const;
    inline UnknownField *mutable_field(int index);
    // Adding fields ---------------------------------------------------
    void AddVarint(int number, uint64_t value);
    void AddFixed32(int number, uint32_t value);
    void AddFixed64(int number, uint64_t value);
    void AddLengthDelimited(int number, const std::string &value);
    std::string *AddLengthDelimited(int number);
    UnknownFieldSet *AddGroup(int number);
    // Parsing helpers -------------------------------------------------
    // These work exactly like the similarly-named methods of Message.
    bool MergeFromCodedStream(io::CodedInputStream *input);
    bool ParseFromCodedStream(io::CodedInputStream *input);
    bool ParseFromZeroCopyStream(io::ZeroCopyInputStream *input);
    bool ParseFromArray(const void *data, int size);
    inline bool ParseFromString(const std::string &data)
    {
        return ParseFromArray(data.data(), static_cast<int>(data.size()));
    }
    // Serialization.
    bool SerializeToString(std::string *output) const;
    bool SerializeToCodedStream(io::CodedOutputStream *output) const;
    static const UnknownFieldSet &default_instance();
};

UnknownField类介绍(重要)
●表示未知字段集中的一个字段。
●类定义在unknown_ field_set.h 中。

class PROTOBUF_EXPORT UnknownField
{
public:
    enum Type
    {
        TYPE_VARINT,
        TYPE_FIXED32,
        TYPE_FIXED64,
        TYPE_LENGTH_DELIMITED,
        TYPE_GROUP
    };
    inline int number() const;
    inline Type type() const;
    // Accessors -------------------------------------------------------
    // Each method works only for UnknownFields of the corresponding type.
    inline uint64_t varint() const;
    inline uint32_t fixed32() const;
    inline uint64_t fixed64() const;
    inline const std::string &length_delimited() const;
    inline const UnknownFieldSet &group() const;
    inline void set_varint(uint64_t value);
    inline void set_fixed32(uint32_t value);
    inline void set_fixed64(uint64_t value);
    inline void set_length_delimited(const std::string &value);
    inline std::string *mutable_length_delimited();
    inline UnknownFieldSet *mutable_group();
};

3.3.2升级通讯录3.1版本--验证未知字段

更新client.cc (通讯录3.1),在这个版本中,需要打印出未知字段的内容。更新的代码如下:

        //打印未知字段:
        const Reflection* reflection = PeopleInfo::GetReflection();
        const UnknownFieldSet& unknowSet = reflection->GetUnknownFields(people);
        for (int j = 0; j < unknowSet.field_count(); j++) {
            const UnknownField& unknow_field = unknowSet.field(j);
            cout << "未知字段" << j+1 << ":"
            << " 字段编号: " << unknow_field.number()
            << " 类型: "<< unknow_field.type();
            switch (unknow_field.type()) {
                case UnknownField::Type::TYPE_VARINT:
                    cout << " 值: " << unknow_field.varint() << endl;
                    break;
                case UnknownField::Type::TYPE_LENGTH_DELIMITED:
                    cout << " 值: " << unknow_field.length_delimited() << endl;
                    break;
            }
        }

其他文件均不用做任何修改,重新编译client.cc,进行一次读操作可得如下结果:

类型为何为 0 ?在介绍 UnknownField 类中讲到了类中包含了未知字段的几种类型:

enum Type {
    TYPE_VARINT,
    TYPE_FIXED32,
    TYPE_FIXED64,
    TYPE_LENGTH_DELIMITED,
    TYPE_GROUP
};

类型为 0,即为 TYPE_VARINT。

2.4前后兼容性

根据上述的例子可以得出,pb是具有向前兼容的。为了叙述方便,把增加了“生日”属性的service
称为‘新模块” ;未做变动的client称为“老模块” 。
●向前兼容:老模块能够正确识别新模块生成或发出的协议。这时新增加的“生日”属性会被当作未
知字段(pb 3.5版本及之后)。
●向后兼容:新模块也能够正确识别老模块生成或发出的协议。
前后兼容的作用:当我们维护- -个很庞大的分布式系统时,由于你无法同时升级所有模块,为了保证
在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼
容”。

3.选项option

.proto文件中可以声明许多选项,使用option 标注。选项能影响proto编译器的某些处理方式。

3.1选项分类

选项的完整列表在google/protobuf/descriptor.proto中定义。部分代码:

syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
...

由此可见,选项分为文件级、消息级、字段级等等,但并没有-种选项能作用于所有的类型。

3.2常用选项列举

●optimize_for :该选项为文件选项,可以设置protoc编译器的优化级别,分别为SPEED、
CODE_ SIZE、 LITE_ RUNTIME。 受该选项影响,设置不同的优化级别,编译.proto文件后生
成的代码内容不同。
SPEED : protoc编译器将生成的代码是高度优化的,代码运行效率高,但是由此生成的代码
编译后会占用更多的空间。SPEED 是默认选项。
CODE_ SIZE : proto编译器将生成最少的类,会占用更少的空间,是依赖基于反射的代码来
实现序列化、反序列化和各种其他操作。但和SPEED恰恰相反,它的代码运行效率较低。这
种方式适合用在包含大量的.proto文件,但并不盲目追求速度的应用中。

LITE_ RUNTIME :生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常
少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供encoding+序列化功能,
所以我们在链接BP库时仅需链接libprotobuf-lite,而非libprotobuf。 这种模式通常用于资源
有限的平台,例如移动手机平台中。

option optimize_for = LITE_RUNTIME;

allow_alias :允许将相同的常量值分配给不同的枚举常量,用来定义别名。该选项为枚举选项。
举个例子:

enum PhoneType {
    option allow_alias = true;
    MP = 0;
    TEL = 1;
    LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195568.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何解决主从数据库同步延迟问题?

如何解决主从数据库同步延迟问题&#xff1f; 前言 最近&#xff0c;系统上频繁出现主从延迟的问题&#xff0c;因此针对主从架构、主从同步以及主从延迟问题进行了一次学习。 主从架构浅析 在了解主从延迟之前&#xff0c;我们有必要对主从架构有一些简单的认识。在如今的…

QT QComBox实现模糊查询

一、概述 在Qt中&#xff0c;可以通过QComboBox和QLineEdit实现模糊查询的功能。模糊查询是指根据用户输入的文本&#xff0c;在下拉框的选项中进行模糊匹配&#xff0c;并动态地显示匹配的选项。 二、基础知识 1、QCompleter (1)QCompleter 是 Qt 框架中提供的一个用于自动…

代码随想录算法训练营 ---第四十六天

第一题&#xff1a; 简介&#xff1a; 本题的重点在于确定背包容量和物品数量 确定dp数组以及下标的含义 dp[i] : 字符串长度为i的话&#xff0c;dp[i]为true&#xff0c;表示可以拆分为一个或多个在字典中出现的单词。 2.确定递推公式 如果确定dp[j] 是true&#xff0c;且…

2019年11月7日 Go生态洞察:Go Modules v2及更高版本

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

应用密码学期末复习(1)

学习资料 应用密码学总结_应用密码学知识点总结-CSDN博客 应用密码学期末复习知识点总结_5的36次方mod97__PriDe的博客-CSDN博客 【密码学】密码学期末考试速成课&#xff0c;不挂科&#xff01;&#xff01;#高数帮_哔哩哔哩_bilibili 目录 学习资料 第一章 概述 1.1信息…

淼一科技为互联网企业销毁硬盘数据 拆除机房设备

在上海这座繁华的大都市&#xff0c;淼一科技以其专业的服务和卓越的技术&#xff0c;为众多互联网企业提供硬盘数据销毁和机房设备拆除服务。作为业界领先的数据安全解决方案提供商&#xff0c;淼一科技致力于保障客户数据的安全与隐私&#xff0c;为客户创造更高的商业价值。…

uniapp前端+python后端=微信小程序支付到底怎么开发???国内的资料为什么没一篇能讲清楚,简简单单的只需要3步就可以了-V2版本

一.微信小程序支付 真的&#xff0c;在接到这个任务的时候&#xff0c;本以为很简单&#xff0c;不就是普通的浏览器复制粘贴&#xff0c;最不济找下gpt给生成一下&#xff0c;但是到实际开发就不同了&#xff0c;不是后端出问题就是前端&#xff0c;搜资料&#xff0c;上百度…

Current request is not a multipart request问题排查

概述 在应用工程里看到如下被标记为deprecated的代码&#xff0c;这对有代码洁癖的我而言是无法忍受的&#xff1a; row.getCell(10).setCellType(Cell.CELL_TYPE_STRING); String hospital row.getCell(0).getStringCellValue();对应的poi版本号&#xff1f;是的&#xff…

适用于iOS 的顶级苹果数据恢复软件

数据丢失可能随时发生在任何人身上&#xff0c;这可能是一种令人沮丧的经历。丢失 iOS 设备上的重要数据可能会造成特别严重的损失&#xff0c;因为其中可能包括有价值的照片、联系人、消息和其他重要文件。幸运的是&#xff0c;有多种数据恢复工具可以帮助用户恢复丢失的数据。…

filebeat日志收集工具

elk:filebeat日志收集工具和logstash相同 filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源要小得多 filebeat可以运行在非Java环境&#xff0c;它可以代理logstash在非Java环境上收集日志 filebeat无法实现数据的过滤&…

定制开发办公软件在企业发展中的优势|app小程序网站搭建

定制开发办公软件在企业发展中的优势|app小程序网站搭建 如今&#xff0c;办公软件已经成为企业日常工作的必需品。很多企业为了提高工作效率和满足自身业务需要&#xff0c;选择定制开发办公软件。下面将介绍定制开发办公软件在企业发展中的优势。 首先&#xff0c;定制开发办…

DjiTello + YoloV5的无人机的抽烟检测

一、效果展示 注&#xff1a;此项目纯作者自己原创&#xff0c;创作不易&#xff0c;不经同意不给予搬运权限&#xff0c;转发前请联系我&#xff0c;源码较大需要者评论获取&#xff0c;谢谢配合&#xff01; 1、未启动飞行模型无人机的目标检测。 DjiTello YOLOV5抽烟检测 …

EDA实验-----正弦信号发生器的设计(Quartus II )

目录 一、实验目的 二、实验仪器 三、实验原理 四、实验内容 五、实验步骤 六、注意事项 七、实验过程&#xff08;操作过程&#xff09; 1.定制LPM_ROM模块 2.定制LPM_ROM元件 3.计数器定制 4.创建锁相环 5.作出电路图 6.顶层设计仿真 一、实验目的 学习使用Ver…

Echarts地图registerMap使用的GeoJson数据获取

https://datav.aliyun.com/portal/school/atlas/area_selector 可以选择省&#xff0c;市&#xff0c;区。 也可以直接在地图上点击对应区域。 我的应用场景 我这里用到这个还是一个特别老的大屏项目&#xff0c;用的jq写的。显示中国地图边界区域 我们在上面的这个地区选择…

【开源】基于Vue+SpringBoot的独居老人物资配送系统

项目编号&#xff1a; S 045 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S045&#xff0c;文末获取源码。} 项目编号&#xff1a;S045&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询社区4…

ELK---filebeat日志收集工具

filebeat日志收集工具 filebeat日志收集工具和logstash相同 filebeat的优点&#xff1a; filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源要小的多 filebeat可以运行在非Java环境。它可以代替logstash在非Java环境上收集…

Java学习路线第一篇:Java基础(2)

这篇则分享Java学习路线第一part&#xff1a;Java基础&#xff08;2&#xff09; 从看到这篇内容开始&#xff0c;你就是被选定的天命骚年&#xff0c;将承担起学完Java基础的使命&#xff0c;本使命为单向契约&#xff0c;你可选择YES或者选择YES。 具体路线安排&#xff1a…

深度学习第1天:深度学习入门-Keras与典型神经网络结构

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 神经网络 介绍 结构 基本要素 Keras 介绍 导入 定义网络 模型训练 前馈神经网络 特点 常见类型 代码示例 反馈神经网络 特点 …

AlphaFold的原理及解读

1、背景 蛋白质是生物体内一类重要的生物大分子&#xff0c;其结构复杂多样&#xff0c;蛋白质的结构对于理解其功能和参与的生物学过程具有重要意义。从生物学角度上看&#xff0c;蛋白质的结构可以分为四个层次&#xff1a;初级结构、二级结构、三级结构和四级结构。 初级结…

企业如何保障跨境金融业务中的数据安全传输?

随着全球化的不断深入&#xff0c;跨境金融业务日益频繁&#xff0c;然而在这些业务中&#xff0c;数据的安全传输一直是企业面临的重大挑战。跨境业务数据传输可能会遇到多种困难&#xff0c;如网络攻击、数据泄露、通信故障等。因此&#xff0c;企业需要采取有效的措施来确保…