• ProtoBuf的使用


    目录

    1.创建.proto文件

    1.1文件规范

    1.2添加注释

    1.3指定proto3语法

    1.4package声明符

    1.5定义消息(message)

    1.6定义消息字段

    2.编译contacts.proto文件

    3.序列化与反序列化的使用


    1.创建.proto文件

    1.1文件规范

    • 创建.proto文件时,⽂件命名应该使用全小写字母命名,多个字⺟之间⽤ _ 连接。例如:
    lower_snake_case.proto 。
    • 书写.proto⽂件代码时,应使用2个空格的缩进。

    1.2添加注释

    向⽂件添加注释,可使⽤ // ?或者? /* ... */ ?

    1.3指定proto3语法

    Protocol Buffers语⾔版本3,简称proto3,是.proto文件最新的语法版本。proto3简化了Protocol
    Buffers语言,既易于使用,又可以在更⼴泛的编程语言中使⽤。它允许你使用Java,C++,Python等多种语言生成protocol buffer代码。
    在.proto文件中,要使用 syntax = "proto3"; 来指定⽂件语法为proto3,并且必须写在除去注释内容的第⼀行。如果没有指定,编译器会使⽤proto2语法。
    在通讯录1.0的contacts.proto文件中,可以为文件指定proto3语法,内容如下:

    syntax = "proto3"; 

    1.4package声明符

    package是一个可选的声明符,能表示.proto文件的命名空间,在项目中要有唯一性。它的作用是为了避免我们定义的消息出现冲突。
    在通讯录1.0的contacts.proto文件中,可以声明其命名空间,内容如下:

    1. syntax = "proto3";
    2. package contacts;

    1.5定义消息(message)

    消息(message) : 要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。
    这里再提一下为什么要定义消息?
    在网络传输中,我们需 要为传输双方定制协议。定制协议说白了就是定义结构体或者结构化数据,
    比如,tcp, udp 报文就是结构化的。
    再比如将数据持久化存储到数据库时,会将一系列元数据统一用对象组织起来,再进行存储。
    所以ProtoBuf就是以message的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使
    用。在通讯录1.0中我们就需要为联系人定义一个message。
    .proto文件中定义一个消息类型的格式为:

    1. message 消息类型名{
    2. }

    消息类型命名规范:使用驼峰命名法,首字母⼤写。

    为contacts.proto (通讯录1.0)新增联系人message, 内容如下:

    1. syntax = "proto3";
    2. package contacts;
    3. // 定义联系⼈消息
    4. message PeopleInfo {
    5. }

    1.6定义消息字段

    在message中我们可以定义其属性字段,字段定义格式为:字段类型字段名=字段唯一编号 ;
    ●字段名称命名规范:全小写字母,多个字母之间用_连接。
    ●字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
    ●字段唯一编号:用来标识字段,一旦开始使用就不能够再改变。

    该表格展示了定义于消息体中的标量数据类型,以及编译.proto文件之后自动生成的类中与之对应的字段类型。在这里展示了与C++语言对应的类型。

    [1]变长编码是指:经过protobuf 编码后,原本4字节或8字节的数可能会被变为其他字节数。

    更新contacts.proto (通讯录1.0),新增姓名、年龄字段:

    1. syntax = "proto3";
    2. package contacts;
    3. message PeopleInfo {
    4. string name = 1;
    5. int32 age = 2;
    6. }

    在这里还要特别讲解一下字段唯一 编号的范围:
    1~ 536,870,911 (2^29-1),其中19000 ~ 19999不可用。
    19000 ~ 19999不可用是因为:在Protobuf协议的实现中,对这些数进行了预留。如果非要在.proto
    文件中使用这些预留标识号,例如将name字段的编号设置为19000,编译时就会报警:

    1. // 消息中定义了如下编号,代码会告警:
    2. // Field numbers 19,000 through 19,999 are reserved for the protobuf implementation
    3. string name = 19000;

    值得一提的是,范围为1~ 15的字段编号需要-一个字节进行编码,16 ~ 2047内的数字需要两个字节进行编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以1 ~ 15要用来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留一些出来。

    2.编译contacts.proto文件

    编译命令
    编译命令行格式为:

    编译contacts.proto文件命令如下:

    protoc --cpp_out=. contacts.proto

    编译contacts.proto文件后,会生成所选择语言的代码,我们选择的是C++,所以编译后生成了两个
    文件: contacts.pb.hI contacts.pb.cc。
    对于编译生成的C++代码,包含了以下内容:
    ●对于每个message,都会生成一个对应的消息类。
    ●在消息类中,编译器为每个字段提供了获取和设置方法,以及一下其他能够操作字段的方法。
    ●编辑器会针对于每个.proto文件生成.h和.cc文件,分别用来存放类的声明与类的实现。
    contacts.pb.h部分代码展示

    1. class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
    2. {
    3. public:
    4. using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
    5. void CopyFrom(const PeopleInfo &from);
    6. using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
    7. void MergeFrom(const PeopleInfo &from)
    8. {
    9. PeopleInfo::MergeImpl(*this, from);
    10. }
    11. static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName()
    12. {
    13. return "PeopleInfo";
    14. }
    15. // string name = 1;
    16. void clear_name();
    17. const std::string &name() const;
    18. template <typename ArgT0 = const std::string &, typename... ArgT>
    19. void set_name(ArgT0 &&arg0, ArgT... args);
    20. std::string *mutable_name();
    21. PROTOBUF_NODISCARD std::string *release_name();
    22. void set_allocated_name(std::string *name);
    23. // int32 age = 2;
    24. void clear_age();
    25. int32_t age() const;
    26. void set_age(int32_t value);
    27. };

    上述的例子中:
    ● 每个字段都有设置和获取的方法,getter的名称与小写字段完全相同,setter方法以set_ 开头。
    ● 每个字段都有一个clear_ 方法,可以将字段重新设置回empty状态。

    contacts.pb.cc中的代码就是对类声明方法的一些实现,在这里就不展开了。
    到这里有同学可能就有疑惑了,那之前提到的序列化和反序列化方法在哪里呢?在消息类的父类
    MessageLite中,提供了读写消息实例的方法,包括序列化方法和反序列化方法。

    1. class MessageLite {
    2. public:
    3. //序列化:
    4. bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件
    5. bool SerializeToArray(void *data, int size) const;
    6. bool SerializeToString(string* output) const;
    7. //反序列化:
    8. bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化
    9. 动作
    10. bool ParseFromArray(const void* data, int size);
    11. bool ParseFromString(const string& data);
    12. };

    注意:
    ●序列化的结果为二进制字节序列,而非文本格式。
    ●以上三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用。
    ●序列化的API函数均为const成员函数,因为序列化不会改变类对象的内容,而是将序列化的结果
    保存到函数入参指定的地址中。

    3.序列化与反序列化的使用

    创建一个测试文件main.cc,方法中我们实现:
    ●对一个联系人的信息使用PB进行序列化,并将结果打印出来。
    ●对序列化后的内容使用PB进行反序列,解析出联系人信息并打印出来。

    main.cc

    1. #include
    2. #include "contacts.pb.h"
    3. using namespace std;
    4. int main()
    5. {
    6. string people_str;
    7. {
    8. //.proto文件声明的package,通过protoc编译后,会为编译生成的C++代码声明同名的命名空间
    9. //范围是在.proto文件中定义的内容
    10. contacts::PeopleInfo people;
    11. people.set_age(20);
    12. people.set_name("张三");
    13. //调用序列化的方法,将序列化后的二进制序列存放到string中
    14. if(!people.SerializeToString(&people_str)) {
    15. cout << "序列化联系人失败" << endl;
    16. }
    17. //打印序列化的结果:
    18. cout << "序列化的结果:" << people_str << endl;
    19. }
    20. {
    21. contacts::PeopleInfo people;
    22. //调用反序列化方法,读取string中存放的二进制序列,并反序列化出对象
    23. if(!people.ParseFromString(people_str)) {
    24. cout << "反序列化联系人失败" << endl;
    25. }
    26. //打印结果:
    27. cout << "联系人年龄:" << people.age() << endl;
    28. cout << "联系人姓名:" << people.name() << endl;
    29. }
    30. return 0;
    31. }

    代码书写完成后,编译main.cc,生成可执行程序TestProtoBuf :

    g++ main.cc contacts.pb.cc -o TestProtoBuf -std=c++11 -lprotobuf 

    ●-lprotobuf: 必加,不然会有链接错误。
    ●-std=c++11: 必加,使用C++11语法。

    执行TestProtoBuf,可以看见people经过序列化和反序列化后的结果: 

    由于ProtoBuf是把联系人对象序列化成了二进制序列,这里用string来作为接收二进制序列的容器。
    所以在终端打印的时候会有换行等一些乱码显示。
    所以相对于xml和JSON来说,因为被编码成二进制,破解成本增大,ProtoBuf 编码是相对安全的。

  • 相关阅读:
    Kotlin基础学习 17
    java 归档版本的下载入口
    DataX DorisWriter 插件DorisStreamLoadObserver类详细解读
    动态规划:区间动态规划
    技术分享| gcc版本升级到5.2
    mac 本地运行 http-proxy-middleware ,请求超时
    java计算机毕业设计小说阅读网站源码+系统+数据库+lw文档+mybatis+运行部署
    《大厂面试》之JVM篇21问与答
    《程序猿笔试题系列》之 有效括号序列
    mysql 8.0.35 搭建主从
  • 原文地址:https://blog.csdn.net/qq_65307907/article/details/133892839