Protocol Buffer技术详解(C++实例)

  http://www.cnblogs.com/stephen-liu74/archive/2013/01/04/2842533.html

这篇Blog仍然是以Google的官方文档为主线,代码实例则完全取自于我们正在开发的一个Demo项目,通过前一段时间的尝试,感觉这种结合的方式比 较有利于培训和内部的技术交流。还是那句话,没有最好的,只有最适合的。我想写Blog也是这一道理吧,不同的技术主题可能需要采用不同的风格。好了,还 是让我们尽早切入主题吧。
    
      一、生成目标语言代码。
      下面的命令帮助我们将MyMessage.proto文件中定义的一组Protocol Buffer格式的消息编译成目标语言(C++)的代码。至于消息的内容,我们会在后面以分段的形式逐一列出,同时也会在附件中给出所有源代码。
      protoc -I=./message --cpp_out=./src ./MyMessage.proto
  
  
从上面的命令行参数中可以看出,待编译的文件为MyMessage.proto,他存放在当前目录的message子目录下。--cpp_out参数则指
示编译工具我们需要生成目标语言是C++,输出目录是当前目录的src子目录。在本例中,生成的目标代码文件名是MyMessage.pb.h和
MyMessage.pb.cc。
    
      二、简单message生成的C++代码。
      这里先定义一个最简单的message,其中只是包含原始类型的字段。
      option optimize_for = LITE_RUNTIME;
      message LogonReqMessage {
          required int64 acctID = 1;
          required string passwd = 2;
      }
  
  
由于我们在MyMessage文件中定义选项optimize_for的值为LITE_RUNTIME,因此由该.proto文件生成的所有C++类的父
类均为::google::protobuf::MessageLite,而非::google::protobuf::Message。在上一篇博客中
已经给出了一些简要的说明,MessageLite类是Message的父类,在MessageLite中将缺少Protocol
Buffer对反射的支持,而此类功能均在Message类中提供了具体的实现。对于我们的项目而言,整个系统相对比较封闭,不会和更多的外部程序进行交
互,与此同时,我们的客户端部分又是运行在Android平台,有鉴于此,我们考虑使用LITE版本的Protocol
Buffer。这样不仅可以得到更高编码效率,而且生成代码编译后所占用的资源也会更少,至于反射所能带来的灵活性和极易扩展性,对于该项目而言完全可以
忽略。下面我们来看一下由message LogonReqMessage生成的C++类的部分声明,以及常用方法的说明性注释。

 1     class LogonReqMessage : public ::google::protobuf::MessageLite {
 2     public:
 3         LogonReqMessage();
 4         virtual ~LogonReqMessage();
 5
 6         // implements Message ----------------------------------------------
 7         //下面的成员函数均实现自MessageLite中的虚函数。
 8         //创建一个新的LogonReqMessage对象,等同于clone。
 9         LogonReqMessage* New() const;
10         //用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=)
11         void CopyFrom(const LogonReqMessage& from);
12         //清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。
13         void Clear();
14         //判断当前状态是否已经初始化。
15         bool IsInitialized() const;
16         //在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。
17         int ByteSize() const;
18         //获取当前对象的类型名称。
19         ::std::string GetTypeName() const;
20
21         // required int64 acctID = 1;
22         //下面的成员函数都是因message中定义的acctID字段而生成。
23         //这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber。
24         static const int kAcctIDFieldNumber = 1;
25         //如果acctID字段已经被设置返回true,否则false。
26         inline bool has_acctid() const;
27         //执行该函数后has_acctid函数将返回false,而下面的acctid函数则返回acctID的缺省值。
28         inline void clear_acctid();
29         //返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。
30         inline ::google::protobuf::int64 acctid() const;
31         //为acctid字段设置新值,调用该函数后has_acctid函数将返回true。
32         inline void set_acctid(::google::protobuf::int64 value);
33
34         // required string passwd = 2;
35         //下面的成员函数都是因message中定义的passwd字段而生成。这里生成的函数和上面acctid
36         //生成的那组函数基本相似。因此这里只是列出差异部分。
37         static const int kPasswdFieldNumber = 2;
38         inline bool has_passwd() const;
39         inline void clear_passwd();
40         inline const ::std::string& passwd() const;
41         inline void set_passwd(const ::std::string& value);
42         //对于字符串类型字段设置const char*类型的变量值。
43         inline void set_passwd(const char* value);
44         inline void set_passwd(const char* value, size_t size);
45         //可以通过返回值直接给passwd对象赋值。在调用该函数之后has_passwd将返回true。
46         inline ::std::string* mutable_passwd();
47         //释放当前对象对passwd字段的所有权,同时返回passwd字段对象指针。调用此函数之后,passwd字段对象
48         //的所有权将移交给调用者。此后再调用has_passwd函数时将返回false。
49         inline ::std::string* release_passwd();
50     private:
51         ... ...
52     };

下面是读写LogonReqMessage对象的C++测试代码和说明性注释。

 1     void testSimpleMessage()
 2     {
 3         printf("==================This is simple message.================\n");
 4         //序列化LogonReqMessage对象到指定的内存区域。
 5         LogonReqMessage logonReq;
 6         logonReq.set_acctid(20);
 7         logonReq.set_passwd("Hello World");
 8         //提前获取对象序列化所占用的空间并进行一次性分配,从而避免多次分配
 9         //而造成的性能开销。通过该种方式,还可以将序列化后的数据进行加密。
10         //之后再进行持久化,或是发送到远端。
11         int length = logonReq.ByteSize();
12         char* buf = new char[length];
13         logonReq.SerializeToArray(buf,length);
14         //从内存中读取并反序列化LogonReqMessage对象,同时将结果打印出来。
15         LogonReqMessage logonReq2;
16         logonReq2.ParseFromArray(buf,length);
17         printf("acctID = %I64d, password = %s\n",logonReq2.acctid(),logonReq2.passwd().c_str());
18         delete [] buf;
19     }

      三、嵌套message生成的C++代码。
      enum UserStatus {
          OFFLINE = 0;
          ONLINE = 1;
      }
      enum LoginResult {
          LOGON_RESULT_SUCCESS = 0;
          LOGON_RESULT_NOTEXIST = 1;
          LOGON_RESULT_ERROR_PASSWD = 2;
          LOGON_RESULT_ALREADY_LOGON = 3;
          LOGON_RESULT_SERVER_ERROR = 4;
      }
      message UserInfo {
          required int64 acctID = 1;
          required string name = 2;
          required UserStatus status = 3;
      }
      message LogonRespMessage {
          required LoginResult logonResult = 1;
          required UserInfo userInfo = 2; //这里嵌套了UserInfo消息。
      }
  
  
对于上述消息生成的C++代码,UserInfo因为只是包含了原始类型字段,因此和上例中的LogonReqMessage没有太多的差别,这里也就不
在重复列出了。由于LogonRespMessage消息中嵌套了UserInfo类型的字段,在这里我们将仅仅给出该消息生成的C++代码和关键性注释。

 1     class LogonRespMessage : public ::google::protobuf::MessageLite {
 2     public:
 3         LogonRespMessage();
 4         virtual ~LogonRespMessage();
 5
 6         // implements Message ----------------------------------------------
 7         ... ... //这部分函数和之前的例子一样。
 8
 9         // required .LoginResult logonResult = 1;
10         //下面的成员函数都是因message中定义的logonResult字段而生成。
11         //这一点和前面的例子基本相同,只是类型换做了枚举类型LoginResult。
12         static const int kLogonResultFieldNumber = 1;
13         inline bool has_logonresult() const;
14         inline void clear_logonresult();
15         inline LoginResult logonresult() const;
16         inline void set_logonresult(LoginResult value);
17
18         // required .UserInfo userInfo = 2;
19         //下面的成员函数都是因message中定义的UserInfo字段而生成。
20         //这里只是列出和非消息类型字段差异的部分。
21         static const int kUserInfoFieldNumber = 2;
22         inline bool has_userinfo() const;
23         inline void clear_userinfo();
24         inline const ::UserInfo& userinfo() const;
25         //可以看到该类并没有生成用于设置和修改userInfo字段set_userinfo函数,而是将该工作
26         //交给了下面的mutable_userinfo函数。因此每当调用函数之后,Protocol Buffer都会认为
27         //该字段的值已经被设置了,同时has_userinfo函数亦将返回true。在实际编码中,我们可以
28         //通过该函数返回userInfo字段的内部指针,并基于该指针完成userInfo成员变量的初始化工作。
29         inline ::UserInfo* mutable_userinfo();
30         inline ::UserInfo* release_userinfo();
31     private:
32         ... ...
33     };                    

下面是读写LogonRespMessage对象的C++测试代码和说明性注释。

 1     void testNestedMessage()
 2     {
 3         printf("==================This is nested message.================\n");
 4         LogonRespMessage logonResp;
 5         logonResp.set_logonresult(LOGON_RESULT_SUCCESS);
 6         //如上所述,通过mutable_userinfo函数返回userInfo字段的指针,之后再初始化该对象指针。
 7         UserInfo* userInfo = logonResp.mutable_userinfo();
 8         userInfo->set_acctid(200);
 9         userInfo->set_name("Tester");
10         userInfo->set_status(OFFLINE);
11         int length = logonResp.ByteSize();
12         char* buf = new char[length];
13         logonResp.SerializeToArray(buf,length);
14
15         LogonRespMessage logonResp2;
16         logonResp2.ParseFromArray(buf,length);
17         printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
18             ,logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());
19         delete [] buf;
20     }    

      四、repeated嵌套message生成的C++代码。
      message BuddyInfo {
          required UserInfo userInfo = 1;
          required int32 groupID = 2;
      }
      message RetrieveBuddiesResp {
          required int32 buddiesCnt = 1;
          repeated BuddyInfo buddiesInfo = 2;
      }
 
   
对于上述消息生成的代码,我们将只是针对RetrieveBuddiesResp消息所对应的C++代码进行详细说明,其余部分和前面小节的例子基本相
同,可直接参照。而对于RetrieveBuddiesResp类中的代码,我们也仅仅是对buddiesInfo字段生成的代码进行更为详细的解释。

 1     class RetrieveBuddiesResp : public ::google::protobuf::MessageLite {
 2     public:
 3         RetrieveBuddiesResp();
 4         virtual ~RetrieveBuddiesResp();
 5
 6         ... ... //其余代码的功能性注释均可参照前面的例子。
 7
 8         // repeated .BuddyInfo buddiesInfo = 2;
 9         static const int kBuddiesInfoFieldNumber = 2;
10         //返回数组中成员的数量。
11         inline int buddiesinfo_size() const;
12         //清空数组中的所有已初始化成员,调用该函数后,buddiesinfo_size函数将返回0。
13         inline void clear_buddiesinfo();
14         //返回数组中指定下标所包含元素的引用。
15         inline const ::BuddyInfo& buddiesinfo(int index) const;
16         //返回数组中指定下标所包含元素的指针,通过该方式可直接修改元素的值信息。
17         inline ::BuddyInfo* mutable_buddiesinfo(int index);
18         //像数组中添加一个新元素。返回值即为新增的元素,可直接对其进行初始化。
19         inline ::BuddyInfo* add_buddiesinfo();
20         //获取buddiesInfo字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。
21         inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >&
22           buddiesinfo() const;
23         //获取buddiesInfo字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。
24         inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*
25           mutable_buddiesinfo();
26     private:
27         ... ...
28     };

下面是读写RetrieveBuddiesResp对象的C++测试代码和说明性注释。

 1     void testRepeatedMessage()
 2     {
 3         printf("==================This is repeated message.================\n");
 4         RetrieveBuddiesResp retrieveResp;
 5         retrieveResp.set_buddiescnt(2);
 6         BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();
 7         buddyInfo->set_groupid(20);
 8         UserInfo* userInfo = buddyInfo->mutable_userinfo();
 9         userInfo->set_acctid(200);
10         userInfo->set_name("user1");
11         userInfo->set_status(OFFLINE);
12
13         buddyInfo = retrieveResp.add_buddiesinfo();
14         buddyInfo->set_groupid(21);
15         userInfo = buddyInfo->mutable_userinfo();
16         userInfo->set_acctid(201);
17         userInfo->set_name("user2");
18         userInfo->set_status(ONLINE);
19
20         int length = retrieveResp.ByteSize();
21         char* buf = new char[length];
22         retrieveResp.SerializeToArray(buf,length);
23
24         RetrieveBuddiesResp retrieveResp2;
25         retrieveResp2.ParseFromArray(buf,length);
26         printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());
27         printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());
28         //这里仅提供了通过容器迭代器的方式遍历数组元素的测试代码。
29         //事实上,通过buddiesinfo_size和buddiesinfo函数亦可循环遍历。
30         RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();
31         RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();
32         for (; it != buddiesInfo->end(); ++it) {
33             printf("BuddyInfo->groupID = %d\n", it->groupid());
34             printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
35                 , it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());
36         }
37         delete [] buf;
38     }

最后需要说明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特别是针对序列化的目的地,比如文件流和网络流等。与此同时,也提供了完整的官方文档和规范的命名规则,在很多情况下,可以直接通过函数的名字便可获悉函数所完成的工作。

时间: 2024-09-29 22:33:02

Protocol Buffer技术详解(C++实例)的相关文章

Protocol Buffer技术详解(Java实例)

Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发团队中目前主要使用的开发语言就是C++.Java和Python,其中Python主要用于编写各种工具程序.然而为了保证该篇Blog的完整性和独立性,我仍然会将上一篇Blog中已经出现的内容再一次赘述,同时对于Java中特有的部分也会着重介绍.          一.生成目标语言代码.      下面

Protocol Buffer技术详解(语言规范)

Protocol Buffer技术详解(语言规范) 该系列Blog的内容主体主要源自于Protocol Buffer的官方文档,而代码示例则抽取于当前正在开发的一个公司内部项目的Demo.这样做的目的主要在于不仅可以保持Google文档的良好风格和系统性,同时再结合一些比较实用和通用的用例,这样就更加便于公司内部的培训,以及和广大网友的技术交流.需要说明的是,Blog的内容并非line by line的翻译,其中包含一些经验性总结,与此同时,对于一些不是非常常用的功能并未予以说明,有兴趣的开发者

红帽Linux故障定位技术详解与实例(2)

红帽Linux故障定位技术详解与实例(2) 2011-09-28 14:26 圈儿 BEAREYES.COM 我要评论(0) 字号:T | T 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 3.内核故障情形及处理 (1)内核panic panic是内

红帽Linux故障定位技术详解与实例(1)

红帽Linux故障定位技术详解与实例(1) 2011-09-28 14:26 圈儿 BEAREYES.COM 我要评论(0) 字号:T | T 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 红帽Linux故障定位技术详解与实例是本文要介绍的内容,主要

红帽Linux故障定位技术详解与实例(3)

红帽Linux故障定位技术详解与实例(3) 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 5.用kdump工具内核故障定位实例 A) 部署Kdump 部署 kdump 收集故障信息的步骤如下: (1)设置好相关的内核启动参数 在 /boot/grub

红帽Linux故障定位技术详解与实例(4)

红帽Linux故障定位技术详解与实例(4) 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 6.使用kprobe来观察内核函数的执行实例 kprobe是SystemTap对内核函数进行probing的功能在内核中的实现,由于内核中提供了正式的API来使

这是一份很有诚意的 Protocol Buffer 语法详解

前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json.XML 真的强!太!多! 由于 Google出品,我相信Protocol Buffer已经具备足够的吸引力 今天,我将详细介绍Protocol Buffer 的语法 & 如何去构建Protocol Buffer 的消息模型 阅读本文前请先阅读: 1. 快来看看Google出品

android DexClassLoader动态加载技术详解

介绍 做项目到一定庞大的时候就会发现方法数太多,安装包根本就装不上去了,这个也不足为奇,我们都知道当方法数目超过65536这个数目限制的时候,挡在2.x的系统上面就会出现无法安装的情况,这个时候动态加载技术就显得非的重要了,我们的项目中为了兼容2.x的手机也是用到了android的动态加载技术,这里我会详细的讲解一下怎么去用,怎么实战,我感觉,空谈理论不如动手来得实在. 实例 下面就通过一个例子反复的说明怎么来实现动态加载,通过不同的方法来调用. 准备工作 1:新建一个java工程(我比较懒我就

实现高性能纠删码引擎 | 纠删码技术详解(下)

作者介绍: 徐祥曦,七牛云工程师,独立开发了多套高性能纠删码/再生码编码引擎.柳青,华中科技大学博士,研究方向为基于纠删码的分布式存储系统. 前言: 在上篇<如何选择纠删码编码引擎>中,我们简单了解了 Reed-Solomon Codes(RS 码)的编/解码过程,以及编码引擎的评判标准.但并没有就具体实现进行展开,本篇作为<纠删码技术详解>的下篇,我们将主要探讨工程实现的问题. 这里先简单提炼一下实现高性能纠删码引擎的要点:首先,根据编码理论将矩阵以及有限域的运算工程化,接下来主