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

转载于:https://www.cnblogs.com/wnnily/p/5979817.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS远程监控

近日&#xff0c;因工作需要&#xff0c;学习了CentOS远程监控的水平有限&#xff0c;多指教。 远程访问CentOS&#xff0c;包括三种方式ssh&#xff0c;telnet&#xff0c;vnc。 本例涉及的是以vnc远程访问CentOS。指令在root下操作。注意&#xff1a;vnc的端口为5900&#xf…

BGP路由协议详解(完整篇)

原文链接&#xff1a;http://xuanbo.blog.51cto.com/499334/465596/ 2010-12-27 12:02:45 上个月我写一篇关于BGP协议的博文&#xff0c;曾许诺过要完善这个文档&#xff0c;但因最近的工作和授课很忙&#xff0c;所以没有时间进行完善。为了实现这个承诺&#xff0c;我在去外…

GitHub+Hexo搭建自己的Blog之-本地环境部署01

前言 之前我的博客没有绑自己的域名&#xff0c;一直在github上放着&#xff0c;访问起来比较麻烦&#xff0c;前阵子在阿里云买了这个域名&#xff0c;配置上之后&#xff0c;就可以通过自己的域名访问了&#xff0c;有些朋友问我这个博客怎么搭的&#xff0c;用的什么主题&am…

使用VS2010编译Qt 5.6.1过程记录

由于Qt官方发布的Qt 5.6.1二进制安装包没有对应VS2010版本的&#xff0c;而我的电脑上只安装了VS2010&#xff0c;因此只能自己编译。 本文记录本人的编译安装过程&#xff0c;以及其中遇到的一些问题。 本文使用VS2010 32位编译Qt 5.6.1。 1. 下载Qt源代码 Qt源代码下载地址&…

Windows安装Apache注册服务出现(OS 5)拒绝访问。 : AH00369: Failed to open the Windows service manager,

windows安装Apache&#xff0c;注册服务出现“(OS 5)拒绝访问。 : AH00369: Failed to open the WinNT service manager..."错误 在安装Apache的时候&#xff0c;我下载的是zip格式&#xff0c;不是msi安装版&#xff0c;需要自己注册服务&#xff0c;才能在桌面任务栏里有…

算法与数据结构(三) 二叉树的遍历及其线索化(Swift版)

前面两篇博客介绍了线性表的顺序存储与链式存储以及对应的操作&#xff0c;并且还聊了栈与队列的相关内容。本篇博客我们就继续聊数据结构的相关东西&#xff0c;并且所涉及的相关Demo依然使用面向对象语言Swift来表示。本篇博客我们就来介绍树结构的一种&#xff1a;二叉树。在…

linux替换某个文件夹下所有文件,Linux 批量查找并替换文件夹下所有文件的内容...

1.批量查找某个目下文件的包含的内容cd etcgrep -rn "查找的内容" ./2.批量替换某个目下所有包含的文件的内容cd etcsed -i "s/查找的内容/替换后的内容/g" grep -rl "查找的内容" ./3.批量查找并替换任意文件夹下的文件内容。sed -i "s/要…

java中String相等问题

判断两个字符串是否相等的问题。在编程中&#xff0c;通常比较两个字符串是否相同的表达式是“”,但在java中不能这么写。在java中&#xff0c;用的是equals(); 例&#xff1a;A字符串和B和字符串比较: if(A.equals(B)){ } 返回true 或false. String 的equals 方法用于比较两个…

WebForm 分页与组合查询

1.封装实体类 2.写查询方法 //SubjectData类 public List<Subject> Select(string name){List<Subject> list new List<Subject>();cmd.CommandText "select *from Subject where SubjectName like a ";cmd.Parameters.Clear();cmd.Parameters.A…

node.js简单爬虫

这里假设你已经安装好node.js和npm&#xff0c;如果没有安装&#xff0c;请参阅其他教程安装。 配置首先是来配置package.json文件&#xff0c;这里使用express,request和cheerio。package.json如下&#xff1a; {"name": "node-scrape","version&quo…

Struts2入门(二)——配置拦截器

一、前言 之前便了解过&#xff0c;Struts 2的核心控制器是一个Filter过滤器&#xff0c;负责拦截所有的用户请求&#xff0c;当用户请求发送过来时&#xff0c;会去检测struts.xml是否存在这个action&#xff0c;如果存在&#xff0c;服务器便会自动帮我们跳转到指定的处理类中…

linux固态机械分区吗,不再疑惑!实测数据后才知道固态硬盘究竟要不要分区

不再疑惑&#xff01;实测数据后才知道固态硬盘究竟要不要分区2019-12-10 20:52:00162点赞594收藏177评论前几年的固态硬盘价格昂贵&#xff0c;一般用户会选择128G或256G的固态作为系统盘&#xff0c;由于单盘空间不大&#xff0c;一般都会配合机械硬盘使用&#xff0c;无需考…

安卓手机的后门控制工具SPADE

SPADE&#xff0c;一款安卓手机的后门控制工具&#xff0c;安全研究人员可以以此了解和研究安卓后门原理。 首先&#xff0c;我们从网站www.apk4fun.com下载apk文件&#xff0c;如ccleaner。然后&#xff0c;我们安装spade git clone https://github.com/suraj-root/spade.git …

MySQL案例-open too many files,MyISAM与partition

-------------------------------------------------------------------------------------------------短文---------------------------------------------------------------------------------------------------------------长话短说~现象: error log中批量刷错误日志, 形…

linux网卡有很多error,教你设置win7系统虚拟机安装linux提示network error的解决方法...

很多朋友在使用电脑的过程中&#xff0c;会发现win7系统虚拟机安装linux提示network error的现象&#xff0c;当遇到win7系统虚拟机安装linux提示network error的问题&#xff0c;我们要怎么解决呢&#xff1f;如今还有很多用户不知道如何处理win7系统虚拟机安装linux提示netwo…

linux模拟网络延迟,使用Nistnet搭建网络延迟模拟设备 (network delay simulator)

mknod /dev/hitbox c 62 0mknod /dev/nistnet c 62 1chown root /dev/hitboxchown root /dev/nistnetmknod /dev/mungebox c 63 0chown root /dev/mungeboxmknod /dev/spybox c 64 0chown root /dev/spyboxmodprobe nistnet可以将这个放到/etc/rc.local中&#xff0c;以便重启后…

将本地Blog部署到GitHub上,有自己的博客页面!

前言 上一篇文章我们已经把本地的hexo环境搭建好了&#xff0c;并且在本地成功预览&#xff0c;但是本地预览也意味着自己的博文只能自己看的到&#xff0c;其他人根本看不到&#xff0c;这篇文章将接上文说一说如何把本地Blog部署到GitHub上&#xff0c;好让小伙伴可以来访问我…

Linux下安装配置JDK

本人使用的VM虚拟机&#xff0c;在VM上安装了Linux&#xff0c;版本是CentOS-6.7-i386-bin-DVD1.iso。 一、下载JDK 在进入JDK官网&#xff0c;找到要下载的JDK版本&#xff0c;将下载地址复制下来&#xff0c;放到迅雷中下载&#xff0c;我下载的是&#xff1a;http://downloa…

新手使用GitHub客户端提交项目的步骤

1.下载https://windows.github.com/ github客户端 2.安装完github&#xff0c;会出现 点击GitHub&#xff0c;Git Shell是命令行指令&#xff0c;暂时用不上 3.点击进入之后 输入你在https://github.com上面注册的用户名和密码点击log in 4.登录之后新建项目 点击左上角…

linux的命令uname n,Linux下uname命令及其选项

Linux下uname命令及其选项2017-03-15 23:22:26晓得了Linux系统的用户信息后&#xff0c;你也可能想晓得所登录的系统信息&#xff0c;今日就绍介获取系统本身信息的命令uname,这搭u应当是UNIX的缩写&#xff0c;操作如次&#xff1a;uname使役uname还可以得到其它相关系统的信息…