Protocol Buffer技术深入理解(C++实例)

这篇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++类的部分声明,以及常用方法的说明性注释。


代码如下:

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

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


代码如下:

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

三、嵌套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++代码和关键性注释。


代码如下:

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

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


代码如下:

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

四、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字段生成的代码进行更为详细的解释。


代码如下:

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

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


代码如下:

void testRepeatedMessage()
{
printf("==================This is repeated message.================\n");
RetrieveBuddiesResp retrieveResp;
retrieveResp.set_buddiescnt(2);
BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();
buddyInfo->set_groupid(20);
UserInfo* userInfo = buddyInfo->mutable_userinfo();
userInfo->set_acctid(200);
userInfo->set_name("user1");
userInfo->set_status(OFFLINE);
buddyInfo = retrieveResp.add_buddiesinfo();
buddyInfo->set_groupid(21);
userInfo = buddyInfo->mutable_userinfo();
userInfo->set_acctid(201);
userInfo->set_name("user2");
userInfo->set_status(ONLINE);
int length = retrieveResp.ByteSize();
char* buf = new char[length];
retrieveResp.SerializeToArray(buf,length);
RetrieveBuddiesResp retrieveResp2;
retrieveResp2.ParseFromArray(buf,length);
printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());
printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());
//这里仅提供了通过容器迭代器的方式遍历数组元素的测试代码。
//事实上,通过buddiesinfo_size和buddiesinfo函数亦可循环遍历。
RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();
RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();
for (; it != buddiesInfo->end(); ++it) {
printf("BuddyInfo->groupID = %d\n", it->groupid());
printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
, it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());
}
delete [] buf;
}

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

本打算将该Blog中使用的示例代码以附件的方式上传,但是没有发现此功能,望谅解。

(0)

相关推荐

  • C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信

    首先来说一下本文中例子所要实现的功能: 基于ProtoBuf序列化对象 使用Socket实现时时通信 数据包的编码和解码 下面来看具体的步骤: 一.Unity中使用ProtoBuf 导入DLL到Unity中, 创建网络传输的模型类: using System; using ProtoBuf; //添加特性,表示可以被ProtoBuf工具序列化 [ProtoContract] public class NetModel { //添加特性,表示该字段可以被序列化,1可以理解为下标 [ProtoMem

  • python如何通过protobuf实现rpc

    由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行google,这里只是做个简单的介绍.rpc的主要功能是让分布式系统的实现更为简单,为提供强大的远程调用而不损失本地调用语义的简洁性.为了实现这个目标,rpc框架需要提供一种透明调用机制让使用者不必显示区分本地调用还是远程调用.rpc架构涉及的组件如下: 客户方像调用本地方法一样去调用远程接口方法,R

  • 使用Protocol Buffers的C语言拓展提速Python程序的示例

    Protocol Buffers (类似XML的一种数据描述语言)最新版本2.3里,protoc-py_out命令只生成原生的Python代码. 尽管PB(Protocol Buffers)可以为C++语言生成快速解析和序列化代码,但是这种方式对于Python不适用,并且手动生成的已包装的代码需要非常大的维护工作.在讨论组里,这是一个常见的功能要求,由于一个必备的客户端组件-AppEngine(根据团队介绍名称为AppEngine),生成原生的Python代码有更高的优先级. 幸运的是, PB

  • 通过Java来测试JSON和Protocol Buffer的传输文件大小

    JSON相信大家都知道是什么东西,如果不知道,那可就真的OUT了,GOOGLE一下去.这里就不介绍啥的了. Protobuffer大家估计就很少听说了,但如果说到是GOOGLE搞的,相信大家都会有兴趣去试一下,毕竟GOOGLE出口,多属精品. Protobuffer是一个类似JSON的一个传输协议,其实也不能说是协议,只是一个数据传输的东西罢了. 那它跟JSON有什么区别呢? 跨语言,这是它的一个优点.它自带了一个编译器,protoc,只需要用它进行编译,可以编译成JAVA.python.C++

  • 基于Protobuf C++ serialize到char*的实现方法分析

    protobuf的Demo程序是 C++版本的protubuf有几种serialize和unSerialize的方法: 方法一: 官方demo程序采用的是 复制代码 代码如下: // Write the new address book back to disk. fstream output(argv[1], ios::out | ios::trunc | ios::binary); if (!address_book.SerializeToOstream(&output)) { cerr &l

  • Protocol Buffer技术深入理解(C++实例)

    这篇Blog仍然是以Google的官方文档为主线,代码实例则完全取自于我们正在开发的一个Demo项目,通过前一段时间的尝试,感觉这种结合的方式比较有利于培训和内部的技术交流.还是那句话,没有最好的,只有最适合的.我想写Blog也是这一道理吧,不同的技术主题可能需要采用不同的风格.好了,还是让我们尽早切入主题吧. 一.生成目标语言代码 下面的命令帮助我们将MyMessage.proto文件中定义的一组Protocol Buffer格式的消息编译成目标语言(C++)的代码.至于消息的内容,我们会在后

  • java多线程编程技术详解和实例代码

     java多线程编程技术详解和实例代码 1.   Java和他的API都可以使用并发. 可以指定程序包含不同的执行线程,每个线程都具有自己的方法调用堆栈和程序计数器,使得线程在与其他线程并发地执行能够共享程序范围内的资源,比如共享内存,这种能力被称为多线程编程(multithreading),在核心的C和C++语言中并不具备这种能力,尽管他们影响了JAVA的设计. 2.   线程的生命周期 新线程的生命周期从"新生"状态开始.程序启动线程前,线程一直是"新生"状态:

  • JSONP 的原理、理解 与 实例分析

    本文实例讲述了JSONP 的原理.理解 与 实例.分享给大家供大家参考,具体如下: 1.什么是jsonp 1.1 同源策略 浏览器同源策略的限制,XmlHttpRequest只允许请求当前源(相同域名.协议.端口)的资源. -1)jsonp只支持get请求 -2)不受同源策略限制 ,兼容性好 不需要XMLHttpRequest(ActiveX)支持,通过js回调函数返回结果 -3)不能解决 不同域的两个页面之间js调用的问题 2. jsonp 原理 动态添加一个script标签,get链接上发送

  • Java反射技术详解及实例解析

    前言 相信很多人都知道反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的,比如最初的很多注解框架,后来因为java反射影响性能,所以被运行时注解APT替代了,java反射有个开源框架jOOR相信很多人都用过,不过我们还是要学习反射的基础语法,这样才能自己写出优秀的框架,当然这里所讲的反射技术,是学习Android插件化技术.Hook技术等必不可少的! 一.基本反射技术   1.1 根据一个字符串得到一个类 getClass方法 String nam

  • Golang Protocol Buffer案例详解

    Golang Protocol Buffer教程 本文介绍如何在Go应用中利用Protocol Buffer数据格式.主要包括什么是Protocol Buffer数据格式,其超越传统数据格式XML或JSON的优势是什么. 1. Protocol Buffer数据格式 Protocol Buffer,本质就是一种数据格式,和JSON或XML一样,不同的语言用于结构化数据序列化或反序列化.该数据格式的优势是较xml或json更小,源于Google.假如我们有一个对象,我们用三种数据结构进行表示: <

  • Java反射技术原理与用法实例分析

    本文实例讲述了Java反射技术原理与用法.分享给大家供大家参考,具体如下: 本文内容: 产生反射技术的需求 反射技术的使用 一个小示例 首发日期:2018-05-10 产生反射技术的需求: 项目完成以后,发现需要增加功能,并且希望增加功能并不需要停止项目运行. 在希望不关停项目运行的情况下,于是考虑到将功能都放到一个单独的项目之外的模块中,每一个功能实现都从这个模块中获取[实际上这个考虑应该是项目开始前就考虑,这个例子可能不是很好].于是就有了反射的产生.(这种思想有点类似工厂模式,如果学过设计

  • Android持久化技术之SharedPreferences存储实例详解

    本文实例讲述了Android持久化技术之SharedPreferences存储.分享给大家供大家参考,具体如下: 1.SharedPreferences存储 在前面一篇文章<Android持久化技术之文件的读取与写入实例详解>中,我们介绍了Android持久化技术的文件的读取与写入.在本文中,继续介绍Android持久化技术另外一个SharedPreferences存储. (1)SharedPreferences存储方式是基于key-value的,通过key可以找到对应的value. (2)支

  • python利用hook技术破解https的实例代码

    相对于http协议,http是的特点就是他的安全性,http协议的通信内容用普通的嗅探器可以捕捉到,但是https协议的内容嗅探到的是加密后的内容,对我们的利用价值不是很高,所以一些大的网站----涉及到"大米"的网站,采用的都是http是协议,嘿嘿,即便这样,还是有办法能看到他的用户名和密码的,嘿嘿,本文只是用于技术学习,只是和大家交流技术,希望不要用于做违法的事情,这个例子是在firefox浏览器下登录https协议的网站,我们预先打开程序,就来了个捕获用户名和密码: 下面是源代码

  • javascript闭包的理解和实例

    顺便提示一下: 词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域. with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope). 下面是一个简单的使用全局变量的闭包实例: 复制代码 代码如下: var sWord="Hello,Welcome to web前端开发工程师的博客,请多多指教." function disWord(){ alert(sWord);

随机推荐