??xml version="1.0" encoding="utf-8" standalone="yes"?> protobuf是google提供的一个开源序列化框架Q类gXMLQJSONq样的数据表CaQ其最大的特点是基于二q制Q因此比传统的XML表示高效短小得多。虽然是二进制数据格式,但ƈ没有因此变得复杂Q开发h员通过按照一定的语法定义l构化的消息格式Q然后送给命o(h)行工P工具自动生成相关的c,可以支持java、c++、python{语a环境。通过这些类包含在项目中Q可以很L的调用相x(chng)法来完成业务消息的序列化与反序列化工作?/p>
protobuf在google中是一个比较核?j)的基础库,作ؓ(f)分布式运涉?qing)到大量的不同业务消息的传递,如何高效z的表示、操作这些业务消息在googleq样的大规模应用中是臛_重要的。而protobufq样的库正好是在效率、数据大、易用性之间取得了(jin)很好的^衡?/p>
更多信息可参?a style="COLOR: rgb(51,102,153); TEXT-DECORATION: none" target=_blank>官方文档 ?a style="COLOR: rgb(51,102,153); TEXT-DECORATION: none" target=_blank>下蝲protobuf-2.3.0.zip源代码库Q下载后解压Q选择vsprojects目录下的protobuf.sln解决Ҏ(gu)打开Q编译整个方案顺利成功。其中有一些测试工E,库相关的工程是libprotobuf、libprotobuf-lite、libprotoc和protoc。其中protoc是命令行工具。在example目录下有一个地址薄消息的例子Q业务消息的定义文g后缀?protoQ其中的addressbook.proto内容为:(x) 该定义文Ӟ定义?jin)地址薄消息的l构Q顶层消息ؓ(f)AddressBookQ其中包含多个Person消息QPerson消息中又包含多个PhoneNumber消息。里面还定义?jin)一个PhoneType的枚丄型?/p>
cd前面有required表示必须Qoptional表示可选,repeated表示重复Q这些定义都是一目了(jin)然的Q无d说。关于消息定义的详细语法可参考官Ҏ(gu)档?br> 现在用命令行工具来生成业务消息类Q切换到protoc.exe所在的debug目录Q在命o(h)行敲入:(x) protoc.exe --proto_path=..\..\examples --cpp_out=..\..\examples ..\..\examples\addressbook.proto 该命令中--proto_path参数表示.proto消息定义文g路径Q?-cpp_out表示输出c++cȝ路径Q后面接着是addressbook.proto消息定义文g。该命o(h)?x)读取addressbook.proto文gq生成对应的c++cd文g和实现文件。执行完后在examples目录生存?sh)(jin)addressbook.pb.h和addressbook.pb.cpp?/p>
现在新徏两个I控制台工程Q第一个不妨叫AddPersonQ然后把examples目录下的add_person.cc、addressbook.pb.h和addressbook.pb.cpp加入到该工程Q另一个工E不妨叫ListPersonQ将examples目录下的list_people.cc、addressbook.pb.h和addressbook.pb.cpp加入到该工程Q在两个工程的项目属性中附加头文件\?./src。两个工E的目依赖都选择libprotobuf工程Q库Q?/p>
lAddPerson工程d一个命令行参数比如叫addressbook.dat用于地址薄信息序列化写入该文Ӟ然后~译q行AddPerson工程Q根据提C入地址薄信? 在ListPerson工程的命令行参数中加d文g参数..\AddPerson\addressbook.datQ然后在q行ListPerson工程Q可?list_people.cc的最后设个断点,避免命o(h)行窗口运行完后关闭看不到l果Q?br> 而读取操作中是address_book.ParseFromIstream从文件流反序列化Q这都是框架自动生成的类中的Ҏ(gu)?/p>
其他操作都是业务消息的字Dset/get之类的对象操作Q很明了(jin)。更详细的API参考官Ҏ(gu)档有详细说明?/p>
从上面的例子可以看出protobufq样的库是很方便高效的,那么自然的想到在|络~程中用来做业务消息的序列化、反序列化支持。在ZUDP协议的网l应用中Q由于UDP本n是有边界Q那么用protobuf来处理业务消息就很方ѝ但在TCP应用中,׃TCP协议没有消息边界Q这需要有一U机制来定业务消息边界。在TCP|络~程中这是必面对的问题?/p>
注意上面的address_book.ParseFromIstream调用Q如果流参数的内容多一个字节或者少一个字节,该方法都?x)返回失败(虽然某些字段可能正确得到l果?jin)?j)Q也是说送给反序列化的数据参数除?jin)格式正还必须有正的大小。因此在tcp|络~程中,要反序列化业务消息,p先知道业务数据的大小。而且在实际应用中可能在一个发送操作中Q发送多个业务消息,而且每个业务消息的大、类型都不一栗而且可能发送很大的数据?hu),比如文g?/p>
昄消息边界的确认问题和protobuf库无养Iq得自己搞定。在官方文档中也提到Qprotobufq不太适合来作大数据的处理Q当业务消息过1MӞ应该考虑是否应该用另外的替代Ҏ(gu)。当然对于大数据Q你也可以分割ؓ(f)多个块用protobuf做小块消息封装进行传递。但对很多应用这L(fng)作法昑־比较多余Q比如发送一个大的文Ӟ一般是在接收方从协议栈收到多少数据写多少数据到磁盘,q是一U边接收边处理的模式,q种模式基本上和每次收到的数据量没有关系。这U模式下再采用分割成消息进行反序列化就昑־多此一举了(jin)?/p>
׃每个业务消息的大和处理方式都可能不一P那么需要独立抽象出一个边界消息来区分不同的业务消息,而且q个边界消息的格式和大小必须固定。对于网l编E熟手,可能早已l想C(jin)q样的消息,我们可以l合protobuf库来定义一个边界消息,不妨叫BoundMsgQ?/p>
可以Ҏ(gu)需要扩充一些字D,但最基本的这两个字段够用了(jin)。我们只需要知道业务消息的cd和大即可。这个消息大是固定?字节Q专门用来确定数据流的边界。有?jin)这L(fng)边界消息Q在接收端处理Q何业务消息就很灵zL便了(jin)Q下面是接收端处理的单伪代码CZQ?/p>
注意上面如果FILE_DATA消息后,q紧接其他业务消息的话,需要小?j),即count累计出的值可能大?/p>
boundMsg.msg_size的|那么多出来的实际上应该是下一个边界消息数据了(jin)。ؓ(f)?jin)避免处理的复杂性,上面所有的循环|络d操作Q上面BO_1QBO_2都可能需要@环读取,Z(jin)化没有写成@环)(j)的缓冲区位置和大参数应该动态调_(d)x(chng)ơ读取时传递的都是q期望读取的数据大小Q对于文件的话,可能Ҏ(gu)点,因ؓ(f)边读取边写入Q就没有必要事先要分配一个文件大的~冲区来存放数据?jin)。对于文件分配一个小~冲区来读,注意认下边界即可?/p>
上面是我的一点考虑Q不妥之处还请大家讨Z。想惛_助于ACE、MINAq样的网l编E框Ӟ然后l合protobufq样的序列化框架Q网l编E中技术基设施层面的东西就l我们解军_差不多了(jin)Q我们可以真正只x(chng)于业务的实现?/p>
转蝲?Protocol Buffers Language Guide之proto文gcd格式分析[关键点翻译] | 漂泊如风protobuf?/h3>
例子介绍
package tutorial;
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;
HOME = 1;
WORK = 2;
}
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.dat文g中?/p>
写入地址薄的操作Q关键操作就是调用address_book.SerializeToOstreamq行序列化到文g?/p>
在TCP|络~程中的考虑
message BoundMsg
{
required int32 msg_type = 1;
required int32 msg_size = 2;
}
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);
....
}
break;
case BO_2:
if(net_read(bo2Buf,boundMsg.msg_size))
{
bo2.ParseFromIstream(bo2Buf);
....
}
break;
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_size;
}
break;
}
}
]]>
]]>
今天来介l一?#8220;Protocol Buffers”Q以下简UprotobufQ这个玩意儿?span id=more-62 style="BORDER-TOP-WIDTH: 0px; PADDING-RIGHT: 0px; PADDING-LEFT: 0px; BORDER-LEFT-WIDTH: 0px; FONT-SIZE: 12px; BORDER-BOTTOM-WIDTH: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; VERTICAL-ALIGN: baseline; PADDING-TOP: 0px; BACKGROUND-COLOR: transparent; BORDER-RIGHT-WIDTH: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; background-origin: initial; background-clip: initial">
?strong style="BORDER-TOP-WIDTH: 0px; PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; BORDER-LEFT-WIDTH: 0px; FONT-SIZE: 12px; BORDER-BOTTOM-WIDTH: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; VERTICAL-ALIGN: baseline; PADDING-TOP: 0px; BACKGROUND-COLOR: transparent; BORDER-RIGHT-WIDTH: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; background-origin: initial; background-clip: initial">protobuf是啥玩意儿?
Z(jin)照顾从没听说q的同学Q照例先来扫盲一把?br>首先Qprotobuf是一个开源项目(官方站点?#8220;q里 ”Q,而且是后台很的开源项目。网上现有的大部分(臛_80%Q开源项目,要么是某人单qӀ要么是几个闲杂人等合伙搞。而protobuf则不?dng)它?鼎鼎大名的Google公司开发出来,q且在Google内部久经考验的一个东东。由此可见,它的作者绝非一般闲杂h{可比?br>那这个听h牛X的东东到底有啥用处捏Q简单地_(d)q个东东q的事儿其实和XML差不多,也就是把某种数据l构的信息,以某U格式保存v来。主要用于数据存储、传输协议格式等 场合。有同学可能?j)理犯嘀咕了(jin)Q放着好好的XML不用Q干嘛重新发明轮子啊Q!先别急,后面然会(x)有说道?br>话说C(jin)dQ大U是08q? 月)(j)QGoogleH然大发慈?zhn)Q把q个好东西A(ch)献给?jin)开源社区。这下,像俺q种喜欢捡现成的家伙可就有福啦!貌似喜欢捡现成的家伙q蛮多滴Q再加上 Google的号召力Q开源后不到一q_(d)protobuf的h气就已经很旺?jin)。所以俺Z(jin)与时pQ就单独开个帖子来忽?zhn)一把?/p>
?strong style="BORDER-TOP-WIDTH: 0px; PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; BORDER-LEFT-WIDTH: 0px; FONT-SIZE: 12px; BORDER-BOTTOM-WIDTH: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; VERTICAL-ALIGN: baseline; PADDING-TOP: 0px; BACKGROUND-COLOR: transparent; BORDER-RIGHT-WIDTH: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; background-origin: initial; background-clip: initial">protobuf有啥特色Q?/strong>
扫盲完了(jin)之后Q就该聊一下技术方面的话题?sh)(jin)。由于这玩意儿发布的旉较短Q未满周岁)(j)Q所以俺接触的时间也不长。今天在此是先学现卖Q列位看官多多包?span class=Apple-converted-space>
◇性能?效率?br>现在Q俺来说说Google公司为啥攄好端端的XML不用Q非要另L(fng)Ӟ重新造轮子。一个根本的原因是XML性能不够好?br>先说旉开销QXML格式化(序列化)(j)的开销倒还好;但是XML解析Q反序列化)(j)的开销׃敢恭l啦。俺之前l常到一些时间性能很敏感的场合Q由于不堪忍受XML解析的速度Q弃之如敝?br>再来看空间开销Q熟(zhn)XML语法的同学应该知道,XML格式Z(jin)有较好的可读性,引入?jin)一些冗余的文本信息。所以空间开销也不是太好(不过q点~点Q俺不常到Q?br>׃Google公司赖以吹嘘的就是它的v量数据和量处理能力。对于几十万、上百万机器的集,动不动就是PBU的数据量,哪怕性能E微提高0.1% 也是相当可观滴。所以Google自然无法容忍XML在性能上的明显~点。再加上Google从来׃~造轮子的牛hQ所以protobuf也就应运而生 ?jin)?br>Google对于性能的偏执,那可是出?jin)名的。所以,俺对于Google搞出来protobuf是非常滴攑ֿ(j)Q性能上不敢说是最好,但肯定不?x)太差?/p>
◇代码生成机?br>除了(jin)性能好,代码生成机制是主要吸引俺的地斏Vؓ(f)?jin)说明这个代码生成机ӞZD个例子?br>比如有个?sh)子商务的系l(假设用C++实现Q,其中的模块A需要发送大量的订单信息l模块BQ通讯的方式用socket?br>假设订单包括如下属性:(x) 然后Q用protobuf内置的编译器~译 该proto。由于本例子的模块是C++Q你可以通过protobuf~译器的命o(h)行参敎ͼ?#8220;q里 ”Q,指定它生成C++语言?#8220;订单包装c?#8221;。(一般来_(d)一个messagel构?x)生成一个包装类Q?br>然后你用类g面的代码来序列化/解析该订单包装类Q?br>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q- // 发送方 string sOrder; Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q- Order order; 有了(jin)q种代码生成机制Q开发h员再也不用吭哧吭哧地~写那些协议解析的代码了(jin)Q干q种zL典型的吃力不讨好Q?br>万一来需求发生变_(d)要求l订单再增加一?#8220;状?#8221;的属性,那只需要在Order.proto文g中增加一行代码。对于发送方Q模块AQ,只要增加一行设|状态的代码Q对于接收方Q模块BQ只要增加一行读取状态的代码。哇塞,直太L?jin)?br>另外Q如果通讯双方使用不同的编E语a来实玎ͼ使用q种机制可以有效保两边的模块对于协议的处理是一致的?br>Z跑题?sh)下?br>从某U意义上Ԍ可以把proto文g看成是描q通讯协议的规D明书Q或者叫接口规范Q。这U伎俩其实老早有?jin),搞过微Y的COM~程或者接触过CORBA的同学,应该都能从中看到IDLQ详l解释看“q里 ”Q的影子。它们的思想是相通滴?/p>
◇支?#8220;向后兼容”?#8220;向前兼容” ◇支持多U编E语a ?strong style="BORDER-TOP-WIDTH: 0px; PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; BORDER-LEFT-WIDTH: 0px; FONT-SIZE: 12px; BORDER-BOTTOM-WIDTH: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; VERTICAL-ALIGN: baseline; PADDING-TOP: 0px; BACKGROUND-COLOR: transparent; BORDER-RIGHT-WIDTH: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; background-origin: initial; background-clip: initial">protobuf有啥~陷Q?/strong>
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
旉QtimeQ用整数表示Q?br>客户idQuseridQ用整数表示Q?br>交易金额QpriceQ用点数表C)(j)
交易的描qͼ(x)descQ用字符串表C)(j)
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
如果使用protobuf实现Q首先要写一个proto文gQ不妨叫Order.protoQ,在该文g中添加一个名?#8221;Order”的messagel构Q用来描q通讯协议中的l构化数据。该文g的内容大致如下:(x)
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
message Order
{
required int32 time = 1;
required int32 userid = 2;
required float price = 3;
optional string desc = 4;
}
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
Order order;
order.set_time(XXXX);
order.set_userid(123);
order.set_price(100.0f);
order.set_desc(“a test order”);
order.SerailzeToString(&sOrder);
// 然后调用某种socket的通讯库把序列化之后的字符串发送出?br>// ……
// 接收?br>string sOrder;
// 先通过|络通讯库接收到数据Q存攑ֈ某字W串sOrder
// ……
if(order.ParseFromString(sOrder)) // 解析该字W串
{
cout << “userid:” << order.userid() << endl
<< “desc:” << order.desc() << endl;
}
else
{
cerr << “parse error!” << endl;
}
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
q是拿刚才的例子来说事儿。ؓ(f)?jin)叙q方便,俺把增加?#8220;状?#8221;属性的订单协议成ؓ(f)“新版?#8221;Q之前的?#8220;老版?#8221;?br>所谓的“向后兼容”Qbackward compatibleQ,是_(d)当模块B升?jin)之后,它能够正识别模块A发出的老版本的协议。由于老版本没?#8220;状?#8221;q个属性,在扩充协议时Q可以考虑?#8220;状?#8221;属性设|成非必?/strong> 的,或者给“状?#8221;属性设|一个缺省|如何讄~省|参见“q里 ”Q?br>所谓的“向前兼容”Qforward compatibleQ,是_(d)当模块A升?jin)之后,模块B能够正常识别模块A发出的新版本的协议。这时候,新增加的“状?#8221;属性会(x)被忽略?br>“向后兼容”?#8220;向前兼容”有啥用捏Q俺举个例子Q当你维护一个很庞大的分布式pȝӞ׃你无?strong style="BORDER-TOP-WIDTH: 0px; PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-WEIGHT: bold; BORDER-LEFT-WIDTH: 0px; FONT-SIZE: 12px; BORDER-BOTTOM-WIDTH: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; VERTICAL-ALIGN: baseline; PADDING-TOP: 0px; BACKGROUND-COLOR: transparent; BORDER-RIGHT-WIDTH: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; background-origin: initial; background-clip: initial">同时 升所?/strong> 模块Qؓ(f)?jin)保证在升q程中,整个pȝ能够可能不受媄(jing)响,需要尽量保证通讯协议?#8220;向后兼容”?#8220;向前兼容”?/p>
俺开博以来点评的几个开源项目(比如“Sqlite ”?#8220;cURL ”Q,都是支持很多U?/strong> ~程语言_(d)q次的protobuf也不例外。在Google官方发布的源代码中包含了(jin)C++、Java、Python三种语言Q正好也是俺最常用的三U,真爽Q。如果你qx(chng)用的是q三U语a之一Q那好办了(jin)?br>假如你想把protobuf用于其它语言Q咋办捏Q由于Google一呼百应的号召力,开源社区对protobuf响应t跃Q近期冒出很多其它编E语a 的版本(比如ActionScript、C#、Lisp、Erlang、Perl、PHP、Ruby{)(j)Q有些语aq同时搞Z(jin)多个开源的目。具体细节可以参?#8220;q里 ”?br>不过俺有义务提醒一下在座的各位同学。如果你考虑把protobuf用于上述q些语言Q一定认真评估对应的开源库。因些开源库不是Google官方提供的、而且出来的时间还?sh)长。所以,它们的质量、性能{方面可能还有欠~?/p>
前几天刚刚在“光环效应 ”的帖子里?#8220;要同时评C点和~点”。所以俺最后再来批判一下这玩意儿的~点?br>◇应用不够广
׃protobuf刚公布没多久Q相比XML而言Qprotobufq属于初?gu)庐。因此,在知名度、应用广度等斚w都远不如XML。由于这个原因,假如你设计的pȝ需要提供若q对外的接口l第三方pȝ调用Q俺奉劝你暂时不要考虑protobuf格式?br>◇二q制格式D可读性差
Z(jin)提高性能Qprotobuf采用?jin)二q制格式q行~码。这直接D?jin)可L差的问题(严格地说Q是没有可读性)(j)。虽然protobuf提供?jin)TextFormatq个工具c(文档?#8220;q里 ”Q,但终I无法彻底解x(chng)问题?br>可读性差的危宻I俺再来D个例子。比如通讯双方如果出现问题Q极易导致扯皮(都不承认自己有问题,都说是对方的错)(j)。俺对付扯皮的一个简单方法就是直?抓包qdump成logQ能比较Ҏ(gu)地看出错误在哪一斏V但是protobuf的二q制格式Q导致你抓包q直接dump出来的log难以看懂?br>◇缺乏自描述
一般来_(d)XML是自描述的,而protobuf格式则不是。给你一D二q制格式的协议内容,如果不配合相应的proto文gQ那直就像天书一般?br>׃“~Z自描q?#8221;Q再加上“二进制格式导致可L差”。所以在配置文g斚wQprotobuf是肯定无法取代XML的地位滴?/p>