反序列化消息时,如果被反序列化的二进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:
false
0
0
repeated
的字段 的默认值是空的( 通常是相应语言的一个空列表 )oneof
字段 和 any
字段 ,C++
和 Java
语言中都有 has_
方法来检测当前字段是否被设置;而对于 标量数据类型,在 proto3
语法下是不会生成 has_
方法的! 如果现有的消息类型已经不再满足我们的需求,例如需要扩展一个字段,在不破坏任何现有代码的情况下更新消息类型非常简单。遵循如下规则即可:
int32
、uint32
、int64
、uint64
和 bool
是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采用与 C++
一致的处理方案(例如若将 64
位整数当做 32
位进行读取,它将被截断为 32
位)。sint32
和 sint64
相互兼容但不与其他的整型兼容。string
和 bytes
在合法 UTF-8
字节前提下也是兼容的。bytes
包含消息编码版本的情况下,嵌套消息与 bytes
也是兼容的。fixed32
与 sfixed32
兼容, fixed64
与 **sfixed64
**兼容。enum
与 int32
,uint32
, int64
和 uint64
兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语言采用不同的处理方案:例如,未识别的 proto3
枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。oneof
: oneof
类型成员之一是安全和二进制兼容的。oneof
类型也是可行的。oneof
类型是不安全的。reserved
),以确保该编号将不能被重复使用。不建议直接删除或注释掉字段。 如果通过【删除】或【注释掉】字段来更新消息类型,未来的用户在添加新字段时,有可能会使用以前已经存在,但已经被删除或注释掉的字段编号,将来使用该 .proto
的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等。
确保不会发生这种情况的一种方法是:使用 reserved
将指定字段的编号或名称设置为保留项 。当我们再使用这些编号或名称时,protobuf
的编译器将会警告这些编号或名称不可用。举个例子:
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
}
还要再说一下的是:因为使用了 reserved
关键字,ProtoBuf
在编译阶段就拒绝了我们使用已经保留的字段编号!
在 protobuf
中,【未知字段】是指接收到的数据中包含的字段,这些字段未在接收端的 .proto
文件定义中描述。比如我们在服务端的 .proto
文件中添加了新字段,这些字段可能没来得及在客户端那边更新,所以客户端那边的 .proto
文件并没有该新字段,那么在接收数据的时候,这个新字段就会被客户端处理为未知字段!
protobuf
的设计允许在不同版本之间进行扩展,而不破坏现有功能,这也是未知字段的一个重要应用场景。
.proto
定义缺少某些字段(例如因为版本较旧),这些字段就被视为未知字段。protobuf
的解析器会跳过这些字段。protobuf
的 C++
或 Java
实现中,支持以某种方式读取、操作或清除未知字段。C++
中,UnknownFieldSet
类可以用于访问和操作未知字段。proto3
中,默认情况下未知字段不会被保留。 下面是消息相关类的关系图:
通常我们通过 google::protobuf::Message
来获取 Reflection
对象和 Description
对象,然后通过它们提供的接口比如 Reflection.GetUnknownFields()
来获取未知字段的集合,每个集合又是一个 UnknownFieldSet
对象,通过它的内部接口即可实现一些操作!
MessageLite
从名字看是轻量级的 Message
,仅仅提供序列化、反序列化功能。
类定义在 google
提供的 message_lite.h 中。
我们自定义的消息类,都是继承自 Message
类。Message
最重要的两个接口是 GetDescriptor
和 GetReflection
,可以获取该类型对应的 Descriptor
对象指针 和 Reflection
对象指针!
类定义在 google
提供的 message.h 中。
// google::protobuf::Message 部分代码展示
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;
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,
iuint64_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
附加到任何消息,可以调用 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();
};
表示未知字段集中的一个字段。该类定义在 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();
};
下面展示之前 2.x
的简化版本,主要看一下打印未知字段的操作:
void PrintfContacts(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 (const PeopleInfo_Phone& phone : people.phone())
cout << "电话" << j++ << ": " << phone.number() << endl;
// 打印未知字段:
const Reflection* reflection = PeopleInfo::GetReflection(); // 获取reflection对象
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;
}
}
}
}
// 运行效果如下所示:
姓名:王五
年龄:0
电话1: 110
未知字段1: 字段编号: 4 类型: 0 值: 1112
根据上述的例子可以得出,protbuf
是具有向前兼容的。为了叙述方便,把增加了“生日”属性的 service
称为“新模块”;未做变动的 client
称为 “老模块”。
pb 3.5
版本及之后)。 前后兼容的作用:当我们维护一个很庞大的分布式系统时,由于你无法同时升级所有模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。
.proto
文件中可以声明许多选项,使用 option
标注。选项能影响 proto
编译器的某些处理方式。
选项的完整列表在 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 消息中
...
由此可见,选项分为 文件级、消息级、字段级 等等, 但并没有一种选项能作用于所有的类型。
optimize_for:该选项为文件选项,可以设置 protoc
编译器的优化级别,分别为 SPEED
、CODE_SIZE
、 LITE_RUNTIME
。受该选项影响,设置不同的优化级别,编译 .proto
文件后生成的代码内容不同。
SPEED
:protoc
编译器将生成的代码是高度优化的,代码运行效率高,但是由此生成的代码编译后会占用更多的空间。 SPEED
是默认选项。
CODE_SIZE
:protoc
编译器将生成最少的类,会占用更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED
恰恰相反,它的代码运行效率较低。这种方式适合用在包含⼤量的 .proto
文件,但并不盲目追求速度的应用中。
LITE_RUNTIME
:生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲 Protocol Buffer
提供的反射功能为代价的,仅仅提供 encoding+序列化
功能,所以我们**在链接 pb
库时仅需链接 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; 这⼀⾏会编译报错
}