protobuf 给结构体变量赋值怎样用 变量 赋值

91技术站91cto.
protobuf在网络编程中的优势及实例
本文介绍了protobuf的基本内容,同时通过实例来介绍其在网络编程中的引用。
先来看看protobuf简介
&&& protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多。虽然是二进制数据格式,但并没有因此变得复杂,开发人员通过按照一定的语法定义结构化的消息格式,然后送给命令行工具,工具将自动生成相关的类,可以支持java、c++、python等语言环境。通过将这些类包含在项目中,可以很轻松的调用相关方法来完成业务消息的序列化与反序列化工作。
protobuf在google中是一个比较核心的基础库,作为分布式运算涉及到大量的不同业务消息的传递,如何高效简洁的表示、操作这些业务消息在google这样的大规模应用中是至关重要的。而protobuf这样的库正好是在效率、数据大小、易用性之间取得了很好的平衡。
更多信息可参考
先源代码库,下载后解压,选择vsprojects目录下的protobuf.sln解决方案打开,编译整个方案顺利成功。其中有一些测试工程,库相关的工程是libprotobuf、libprotobuf-lite、libprotoc和protoc。其中protoc是命令行工具。在example目录下有一个地址薄消息的例子,业务消息的定义文件后缀为.proto,其中的addressbook.proto内容为:
option java_package = &com.example.tutorial&;
option java_outer_classname = &AddressBookProtos&;
message Person {
required string name = 1;
required int32 id = 2; // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
MOBILE = 0;
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
repeated PhoneNumber phone = 4;
// Our address book file is just one of these.
message AddressBook {
repeated Person person = 1;
该定义文件,定义了地址薄消息的结构,顶层消息为AddressBook,其中包含多个Person消息,Person消息中又包含多个PhoneNumber消息。里面还定义了一个PhoneType的枚举类型。
类型前面有required表示必须,optional表示可选,repeated表示重复,这些定义都是一目了然的,无须多说。关于消息定义的详细语法可参考官方文档。
现在用命令行工具来生成业务消息类,切换到protoc.exe所在的debug目录,在命令行敲入:
protoc.exe --proto_path=../../examples --cpp_out=../../examples ../../examples/addressbook.proto
该命令中--proto_path参数表示.proto消息定义文件路径,--cpp_out表示输出c++类的路径,后面接着是addressbook.proto消息定义文件。该命令会读取addressbook.proto文件并生成对应的c++类头文件和实现文件。执行完后在examples目录生存了addressbook.pb.h和addressbook.pb.cpp。
现在新建两个空控制台工程,第一个不妨叫AddPerson,然后把examples目录下的add_person.cc、addressbook.pb.h和addressbook.pb.cpp加入到该工程,另一个工程不妨叫ListPerson,将examples目录下的list_people.cc、addressbook.pb.h和addressbook.pb.cpp加入到该工程,在两个工程的项目属性中附加头文件路径../src。两个工程的项目依赖都选择libprotobuf工程(库)。
给AddPerson工程添加一个命令行参数比如叫addressbook.dat用于将地址薄信息序列化写入该文件,然后编译运行AddPerson工程,根据提示输入地址薄信息:
输入完成后,将序列化到addressbook.dat文件中。
在ListPerson工程的命令行参数中加读取文件参数../AddPerson/addressbook.dat,然后在运行ListPerson工程,可在 list_people.cc的最后设个断点,避免命令行窗口运行完后关闭看不到结果:
写入地址薄的操作,关键操作就是调用address_book.SerializeToOstream进行序列化到文件流。
而读取操作中就是address_book.ParseFromIstream从文件流反序列化,这都是框架自动生成的类中的方法。
其他操作都是业务消息的字段set/get之类的对象级操作,很明了。更详细的API参考官方文档有详细说明。
在TCP网络编程中的应用:
从上面的例子可以看出protobuf这样的库是很方便高效的,那么自然的想到在网络编程中用来做业务消息的序列化、反序列化支持。在基于UDP协议的网络应用中,由于UDP本身是有边界,那么用protobuf来处理业务消息就很方便。但在TCP应用中,由于TCP协议没有消息边界,这就需要有一种机制来确定业务消息边界。在TCP网络编程中这是必须面对的问题。
注意上面的address_book.ParseFromIstream调用,如果流参数的内容多一个字节或者少一个字节,该方法都会返回失败(虽然某些字段可能正确得到结果了),也就是说送给反序列化的数据参数除了格式正确还必须有正确的大小。因此在tcp网络编程中,要反序列化业务消息,就要先知道业务数据的大小。而且在实际应用中可能在一个发送操作中,发送多个业务消息,而且每个业务消息的大小、类型都不一样。而且可能发送很大的数据流,比如文件。
显然消息边界的确认问题和protobuf库无关,还得自己搞定。在官方文档中也提到,protobuf并不太适合来作大数据的处理,当业务消息超过1M时,就应该考虑是否应该用另外的替代方案。当然对于大数据,你也可以分割为多个小块用protobuf做小块消息封装进行传递。但对很多应用这样的作法显得比较多余,比如发送一个大的文件,一般是在接收方从协议栈收到多少数据就写多少数据到磁盘,这是一种边接收边处理的流模式,这种模式基本上和每次收到的数据量没有关系。这种模式下再采用分割成小消息进行反序列化就显得多此一举了。
由于每个业务消息的大小和处理方式都可能不一样,那么就需要独立抽象出一个边界消息来区分不同的业务消息,而且这个边界消息的格式和大小必须固定。对于网络编程熟手,可能早已经想到了这样的消息,我们可以结合protobuf库来定义一个边界消息,不妨叫BoundMsg:
message BoundMsg
required int32 msg_type = 1;
required int32 msg_size = 2;
可以根据需要扩充一些字段,但最基本的这两个字段就够用了。我们只需要知道业务消息的类型和大小即可。这个消息大小是固定的8字节,专门用来确定数据流的边界。有了这样的边界消息,在接收端处理任何业务消息就很灵活方便了,下面是接收端处理的简单伪代码示例:
if(net_read(buf,8))
boundMsg.ParseFromIstream(buf);
switch(boundMsg.msg_type)
case BO_1:
if(net_read(bo1Buf,boundMsg.msg_size))
bo1.ParseFromIstream(bo1Buf);
case BO_2:
if(net_read(bo2Buf,boundMsg.msg_size))
bo2.ParseFromIstream(bo2Buf);
case FILE_DATA:
count = 0;
while(count & boundMsg.msg_size)
piece_size = net_read(fileBuf,1024);
write_file(filename,fileBuf,piece_size);
count = count + piece_
注意上面如果FILE_DATA消息后,还紧接其他业务消息的话,需要小心,即count累计出的值可能大于
boundMsg.msg_size的值,那么多出来的实际上应该是下一个边界消息数据了。为了避免处理的复杂性,上面所有的循环网络读取操作(上面BO_1,BO_2都可能需要循环读取,为了简化没有写成循环)的缓冲区位置和大小参数应该动态调整,即每次读取时传递的都是还期望读取的数据大小,对于文件的话,可能特殊点,因为边读取边写入,就没有必要事先要分配一个文件大小的缓冲区来存放数据了。对于文件分配一个小缓冲区来读,注意确认下边界即可。
上面是我的一点考虑,不妥之处还请大家讨论交流。想想借助于ACE、MINA这样的网络编程框架,然后结合protobuf这样的序列化框架,网络编程中技术基础设施层面的东西就给我们解决得差不多了,我们可以真正只关注于业务的实现。原文地址:91CTO
相关文章:
系统架构类热门文章
系统架构类最新文章二次元同好交流新大陆
扫码下载App
汇聚2000万达人的兴趣社区下载即送20张免费照片冲印
扫码下载App
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
class LogonReqMessage : public ::google::protobuf::MessageLite {&public:&LogonReqMessage();&virtual ~LogonReqMessage();&// implements Message ----------------------------------------------&//下面的成员函数均实现自MessageLite中的虚函数。&//创建一个新的LogonReqMessage对象,等同于clone。&LogonReqMessage* New()&//用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=)&void CopyFrom(const LogonReqMessage& from);&//清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。&void Clear();&//判断当前状态是否已经初始化。&bool IsInitialized()&//在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。&int ByteSize()&//获取当前对象的类型名称。&::std::string GetTypeName()&// required int64 acctID = 1;&//下面的成员函数都是因message中定义的acctID字段而生成。&//这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber。&static const int kAcctIDFieldNumber = 1;&//如果acctID字段已经被设置返回true,否则false。&inline bool has_acctid()&//执行该函数后has_acctid函数将返回false,而下面的acctid函数则返回acctID的缺省值。&inline void clear_acctid();&//返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。&inline ::google::protobuf::int64 acctid()&//为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()&inline void clear_passwd();&inline const ::std::string& passwd()&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 logonR&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 []&}&三、嵌套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()&inline void clear_logonresult();&inline LoginResult logonresult()&inline void set_logonresult(LoginResult value);&// required .UserInfo userInfo = 2;&//下面的成员函数都是因message中定义的UserInfo字段而生成。&//这里只是列出和非消息类型字段差异的部分。&static const int kUserInfoFieldNumber = 2;&inline bool has_userinfo()&inline void clear_userinfo();&inline const ::UserInfo& userinfo()&//可以看到该类并没有生成用于设置和修改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 logonR&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 []&}&四、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()&//清空数组中的所有已初始化成员,调用该函数后,buddiesinfo_size函数将返回0。&inline void clear_buddiesinfo();&//返回数组中指定下标所包含元素的引用。&inline const ::BuddyInfo& buddiesinfo(int index)&//返回数组中指定下标所包含元素的指针,通过该方式可直接修改元素的值信息。&inline ::BuddyInfo* mutable_buddiesinfo(int index);&//像数组中添加一个新元素。返回值即为新增的元素,可直接对其进行初始化。&inline ::BuddyInfo* add_buddiesinfo();&//获取buddiesInfo字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。&inline const ::google::protobuf::RepeatedPtrField& ::BuddyInfo &&&buddiesinfo()&//获取buddiesInfo字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。&inline ::google::protobuf::RepeatedPtrField& ::BuddyInfo &*&mutable_buddiesinfo();&private:&... ...&};&下面是读写RetrieveBuddiesResp对象的C++测试代码和说明性注释。&复制代码 代码如下:void testRepeatedMessage()&{&printf("==================This is repeated message.================\n");&RetrieveBuddiesResp retrieveR&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 []&}&最后需要说明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特别是针对序列化的目的地,比如文件流和网络流等。与此同时,也提供了完整的官方文档和规范的命名规则,在很多情况下,可以直接通过函数的名字便可获悉函数所完成的工作。详细出处参考:http://www.jb51.net/article/33030.htm
阅读(3927)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_',
blogTitle:'protobuf文件嵌套结构实例',
blogAbstract:'class LogonReqMessage : public ::google::protobuf::MessageLite {&public:&LogonReqMessage();&virtual ~LogonReqMessage();&// implements Message ----------------------------------------------&//下面的成员函数均实现自MessageLite中的虚函数。&//创建一个新的LogonReqMessage对象,等同于clone。&LogonReqMessage* New()&',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:0,
publishTime:0,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'',
hmcon:'0',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}

我要回帖

更多关于 怎么给结构体变量赋值 的文章

 

随机推荐