网站开发与维护是学什么,恩施旅游网站建设,wordpress返回html原理,html5手机商城网站模板1 #x1f351;字段规则#x1f351;
消息的字段可以⽤下⾯⼏种规则来修饰#xff1a;
singular #xff1a;消息中可以包含该字段零次或⼀次#xff08;不超过⼀次#xff09;。 proto3 语法中#xff0c;字段默认使⽤该规则。repeated #xff1a;消息中可以包含该…1 字段规则
消息的字段可以⽤下⾯⼏种规则来修饰
singular 消息中可以包含该字段零次或⼀次不超过⼀次。 proto3 语法中字段默认使⽤该规则。repeated 消息中可以包含该字段任意多次包括零次其中重复值的顺序会被保留。可以理解为定义了⼀个数组。
比如
syntax proto3;
package contacts;
message PeopleInfo
{string name 1; int32 age 2; repeated string phone_numbers 3;
}2 消息类型的定义与使用
2.1 定义 在单个 .proto ⽂件中可以定义多个消息体且⽀持定义嵌套类型的消息任意多层。每个消息体中的字段编号可以重复。 嵌套写法:
syntax proto3;
package contacts;
message PeopleInfo
{string name 1; int32 age 2; message Phone {string number 1;}
}⾮嵌套写法:
syntax proto3;
package contacts;
message Phone
{string number 1;
}
message PeopleInfo
{string name 1; int32 age 2;
}2.2 使用
消息类型可作为字段类型使⽤
syntax proto3;
package contacts;
message PeopleInfo
{string name 1; int32 age 2; message Phone {string number 1; }repeated Phone phone 3;
}可导⼊其他 .proto ⽂件的消息并使⽤ 例如 Phone 消息定义在 phone.proto ⽂件中 syntax proto3;
package phone;
message Phone
{string number 1;
}contacts.proto 中的 PeopleInfo 使⽤ Phone 消息
syntax proto3;
package contacts;
import phone.proto; // 使⽤ import 将 phone.proto ⽂件导⼊进来 !!!
message PeopleInfo
{string name 1; int32 age 2; // 引⼊的⽂件声明了package使⽤消息时需要⽤ ‘命名空间.消息类型’ 格式 repeated phone.Phone phone 3;
}注在 proto3 ⽂件中可以导⼊ proto2 消息类型并使⽤它们反之亦然。 3 通讯录版本v2
contacts.proto
syntax proto3;
package contacts;// 联系⼈
message PeopleInfo
{string name 1; // 姓名int32 age 2; // 年龄message Phone {string number 1; // 电话号码}repeated Phone phone 3; // 电话
}
// 通讯录
message Contacts
{repeated PeopleInfo contacts 1;
}接着进⾏⼀次编译 protoc --cpp_out. contacts.proto3.1 通讯录v2的写入实现
main.cc:
#include iostream
#include fstream
#include contacts.pb.h
using namespace std;
using namespace contacts;
/*** 新增联系⼈*/
void AddPeopleInfo(PeopleInfo *people_info_ptr)
{cout -------------新增联系⼈------------- endl;cout 请输⼊联系⼈姓名: ;string name;getline(cin, name);people_info_ptr-set_name(name);cout 请输⼊联系⼈年龄: ;int age;cin age;people_info_ptr-set_age(age);cin.ignore(256, \n);//遇到\n结束或者接受到的数据大于256for (int i 1;; i){cout 请输⼊联系⼈电话 i (只输⼊回⻋完成电话新增): ;string number;getline(cin, number);if (number.empty()){break;}PeopleInfo_Phone *phone people_info_ptr-add_phone();phone-set_number(number);}cout -----------添加联系⼈成功----------- endl;
}
int main(int argc, char *argv[])
{// GOOGLE_PROTOBUF_VERIFY_VERSION 宏: 验证没有意外链接到与编译的头⽂件不兼容的库版// 本。如果检测到版本不匹配程序将中⽌。注意每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使// ⽤ C Protocol Buffer 库之前执⾏此宏是⼀种很好的做法但不是绝对必要的。GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc ! 2){cerr Usage: argv[0] CONTACTS_FILE endl;return -1;}Contacts contacts;// 先读取已存在的 contactsfstream input(argv[1], ios::in | ios::binary);if (!input){cout argv[1] : File not found. Creating a new file. endl;}else if (!contacts.ParseFromIstream(input)){cerr Failed to parse contacts. endl;input.close();return -1;}// 新增⼀个联系⼈AddPeopleInfo(contacts.add_contacts());// 向磁盘⽂件写⼊新的 contactsfstream output(argv[1], ios::out | ios::trunc | ios::binary);if (!contacts.SerializeToOstream(output)){cerr Failed to write contacts. endl;input.close();output.close();return -1;}input.close();output.close();google::protobuf::ShutdownProtobufLibrary();return 0;
}注意点代码中有解释。
验证 查看⼆进制⽂件
3.2 通讯录v2的读取实现
#include iostream
#include fstream
#include contacts.pb.h
using namespace std;
using namespace contacts;
/*** 打印联系⼈列表*/
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;}}
}
int main(int argc, char *argv[])
{GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc ! 2){cerr Usage: argv[0] CONTACTS_FILE endl;return -1;}// 以⼆进制⽅式读取 contactsContacts contacts;fstream input(argv[1], ios::in | ios::binary);if (!contacts.ParseFromIstream(input)){cerr Failed to parse contacts. endl;input.close();return -1;}// 打印 contactsPrintfContacts(contacts);input.close();google::protobuf::ShutdownProtobufLibrary();return 0;
}验证
另⼀种验证⽅法--decode 我们可以⽤ protoc -h 命令来查看 ProtoBuf 为我们提供的所有命令 option。其中 ProtoBuf 提供⼀个命令选项 --decode 表⽰从标准输⼊中读取给定类型的⼆进制消息并将其以⽂本格式写⼊标准输出。 消息类型必须在 .proto ⽂件或导⼊的⽂件中定义。 4 enum 类型
4.1 定义规则
语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为
枚举类型名称使⽤驼峰命名法⾸字⺟⼤写。 例如 MyEnum常量值名称全⼤写字⺟多个字⺟之间⽤ _ 连接。例如 ENUM_CONST 0;
我们可以定义⼀个名为 PhoneType 的枚举类型定义如下
enum PhoneType
{MP 0; // 移动电话TEL 1; // 固定电话
}要注意枚举类型的定义有以下⼏种规则
0 值常量必须存在且要作为第⼀个元素。这是为了与 proto2 的语义兼容第⼀个元素作为默认值且值为 0。枚举类型可以在消息外定义也可以在消息体内定义嵌套。枚举的常量值在 32 位整数的范围内但因负值⽆效因⽽不建议使⽤与编码规则有关。
4.2 注意事项
将两个 具有相同枚举值名称 的枚举类型放在单个 .proto ⽂件下测试时编译后会报错某某某常量已经被定义所以这⾥要注意
同级同层的枚举类型各个枚举类型中的常量不能重名。单个 .proto ⽂件下最外层枚举类型和嵌套枚举类型不算同级。多个 .proto ⽂件下若⼀个⽂件引⼊了其他⽂件且每个⽂件都未声明 package每个 proto ⽂件中的枚举类型都在最外层算同级。多个 .proto ⽂件下若⼀个⽂件引⼊了其他⽂件且每个⽂件都声明了 package不算同级。 5 Any 类型
字段还可以声明为 Any 类型可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤ repeated 来修饰。 Any 类型是 google 已经帮我们定义好的类型在安装 ProtoBuf 时其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件。 此时我们可以再升级通讯录版本 .proto文件
syntax proto3;
package contacts;
import google/protobuf/any.proto; // 引⼊ any.proto ⽂件
// 地址
message Address
{string home_address 1; // 家庭地址string unit_address 2; // 单位地址
}
// 联系⼈
message PeopleInfo
{string name 1; // 姓名int32 age 2; // 年龄message Phone {string number 1; // 电话号码enum PhoneType {MP 0; // 移动电话TEL 1; // 固定电话}PhoneType type 2; // 类型}repeated Phone phone 3; // 电话google.protobuf.Any data 4;
}
// 通讯录
message Contacts
{repeated PeopleInfo contacts 1;
}使用protoc编译器编译后
// 新⽣成的 Address 类
class Address final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;void CopyFrom(const Address from);using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;void MergeFrom( const Address from) {Address::MergeImpl(*this, from);}// string home_address 1;void clear_home_address();const std::string home_address() const;template typename ArgT0 const std::string, typename... ArgTvoid set_home_address(ArgT0 arg0, ArgT... args);std::string* mutable_home_address();PROTOBUF_NODISCARD std::string* release_home_address();void set_allocated_home_address(std::string* home_address);// string unit_address 2;void clear_unit_address();const std::string unit_address() const;template typename ArgT0 const std::string, typename... ArgTvoid set_unit_address(ArgT0 arg0, ArgT... args);std::string* mutable_unit_address();PROTOBUF_NODISCARD std::string* release_unit_address();void set_allocated_unit_address(std::string* unit_address);
};
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:// .google.protobuf.Any data 4;bool has_data() const;void clear_data();const ::PROTOBUF_NAMESPACE_ID::Any data() const;PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_data();::PROTOBUF_NAMESPACE_ID::Any* mutable_data();void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Any* data);
};上述的代码中对于 Any 类型字段设置和获取获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法可以使⽤ mutable_ ⽅法返回值为Any类型的指针这类⽅法会为我们开辟好空间可以直接对这块空间的内容进⾏修改。
之前讲过我们可以在 Any 字段中存储任意消息类型这就要涉及到任意消息类型 和 Any 类型的互转。这部分代码就在 Google为我们写好的头⽂件 any.pb.h 中。对 any.pb.h 部分代码展⽰
class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message
{bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message message) {...}bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const {...}templatetypename T bool Is() const {return _impl_._any_metadata_.IsT();}
};上述代码中
使⽤ PackFrom() ⽅法可以将任意消息类型转为 Any 类型。使⽤ UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型。使⽤ Is() ⽅法可以⽤来判断存放的消息类型是否为 typename T。 6 oneof 类型
如果消息中有很多可选字段 并且将来同时只有⼀个字段会被设置 那么就可以使⽤ oneof 加强这个⾏为也能有节约内存的效果。
注意:
可选字段中的字段编号不能与⾮可选字段的编号冲突。不能在 oneof 中使⽤ repeated 字段。将来在设置 oneof 字段中值时如果将 oneof 中的字段设置多个那么只会保留最后⼀次设置的成员之前设置的 oneof 成员会⾃动清除。
contacts.pb.h 更新的部分代码展⽰
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{enum OtherContactCase {kQq 5,kWeixin 6,OTHER_CONTACT_NOT_SET 0,};// string qq 5;bool has_qq() const;void clear_qq();const std::string qq() const;template typename ArgT0 const std::string, typename... ArgTvoid set_qq(ArgT0 arg0, ArgT... args);std::string* mutable_qq();PROTOBUF_NODISCARD std::string* release_qq();void set_allocated_qq(std::string* qq);// string weixin 6;bool has_weixin() const;void clear_weixin();const std::string weixin() const;template typename ArgT0 const std::string, typename... ArgTvoid set_weixin(ArgT0 arg0, ArgT... args);std::string* mutable_weixin();PROTOBUF_NODISCARD std::string* release_weixin();void set_allocated_weixin(std::string* weixin);void clear_other_contact();OtherContactCase other_contact_case() const;
};上述的代码中对于 oneof 字段
会将 oneof 中的多个字段定义为⼀个枚举类型。设置和获取对 oneof 内的字段进⾏常规的设置和获取即可但要注意只能设置⼀个。如果设置多个那么只会保留最后⼀次设置的成员。清空 oneof 字段clear_ ⽅法获取当前设置了哪个字段_case ⽅法 7 map 类型
语法⽀持创建⼀个关联映射字段也就是可以使⽤ map 类型去声明字段类型格式为
mapkey_type, value_type map_field N;要注意的是
key_type 是除了 float 和 bytes 类型以外的任意标量类型。 value_type 可以是任意类型。map 字段不可以⽤ repeated 修饰。map 中存⼊的元素是⽆序的。
contacts.pb.h 更新的部分代码展⽰
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{// mapstring, string remark 7;int remark_size() const;void clear_remark();const ::PROTOBUF_NAMESPACE_ID::Map std::string, std::string remark() const;::PROTOBUF_NAMESPACE_ID::Map std::string, std::string *mutable_remark();
};上述的代码中对于Map类型的字段
清空map: clear_ ⽅法。设置和获取获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法为 mutable_ ⽅法返回值为Map类型的指针这类⽅法会为我们开辟好空间可以直接对这块空间的内容进⾏修改。 8 默认值
反序列化消息时如果被反序列化的⼆进制序列中不包含某个字段反序列化对象中相应字段时就会设置为该字段的默认值。不同的类型对应的默认值不同
对于字符串默认值为空字符串。对于字节默认值为空字节。对于布尔值默认值为 false。对于数值类型默认值为 0。对于枚举默认值是第⼀个定义的枚举值 必须为 0。对于消息字段未设置该字段。它的取值是依赖于语⾔。对于设置了 repeated 的字段的默认值是空的 通常是相应语⾔的⼀个空列表 。对于 消息字段 、 oneof字段 和 any字段 C 和 Java 语⾔中都有 has_ ⽅法来检测当前字段是否被设置。 9 更新消息
9.1 更新规则
如果现有的消息类型已经不再满⾜我们的需求例如需要扩展⼀个字段在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可
禁⽌修改任何已有字段的字段编号。若是移除⽼字段要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号reserved以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。int32 uint32 int64 uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配会采⽤与 C ⼀致的处理⽅案例如若将 64 位整数当做 32 位进⾏读取它将被截断为 32 位。sint32 和 sint64 相互兼容但不与其他的整型兼容。string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。bytes 包含消息编码版本的情况下嵌套消息与 bytes 也是兼容的。fixed32 与 sfixed32 兼容 fixed64 与 sfixed64兼容。enum 与 int32uint32 int64 和 uint64 兼容注意若值不匹配会被截断。但要注意当反序列化消息时会根据语⾔采⽤不同的处理⽅案例如未识别的 proto3 枚举类型会被保存在消息中但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。oneof 将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的。 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的。 将任何字段移⼊已存在的 oneof 类型是不安全的。
9.2 保留字段 reserved
如果通过 删除 或 注释掉 字段来更新消息类型未来的⽤⼾在添加新字段时有可能会使⽤以前已经存在但已经被删除或注释掉的字段编号。将来使⽤该 .proto 的旧版本时的程序会引发很多问题数据损坏、隐私错误等等。
确保不会发⽣这种情况的⼀种⽅法是使⽤ reserved 将指定字段的编号或名称设置为保留项 。当我们再使⽤这些编号或名称时protocol buffer 的编译器将会警告这些编号或名称不可⽤。
9.3 未知字段
未知字段解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表⽰⽅式。例如当旧程序解析带有新字段的数据时这些新字段就会成为旧程序的未知字段。
本来proto3 在解析消息时总是会丢弃未知字段但在 3.5 版本中重新引⼊了对未知字段的保留机制。所以在 3.5 或更⾼版本中未知字段在反序列化时会被保留同时也会包含在序列化的结果中。
9.4 前后兼容性
向前兼容⽼模块能够正确识别新模块⽣成或发出的协议。向后兼容新模块也能够正确识别⽼模块⽣成或发出的协议。
前后兼容的作⽤当我们维护⼀个很庞⼤的分布式系统时由于你⽆法同时 升级所有 模块为了保证在升级过程中整个系统能够尽可能不受影响就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。 10 选项 option
.proto ⽂件中可以声明许多选项使⽤ option 标注。选项能影响 proto 编译器的某些处理⽅式。
10.1 常用选项列举
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; 这⼀⾏会编译报错
}