protocol buffers 使用方法

为什么使用 Protocol Buffers

我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字、ID、邮件地址和联系电话。

如何序列化和获取结构化的数据?这里有几种解决方案:

  • 以二进制形式发送/接收原生的内存数据结构。通常,这是一种脆弱的方法,因为接收/读取代码必须基于完全相同的内存布局、大小端等环境进行编译。同时,当文件增加时,原始格式数据会随着与该格式相关的软件而迅速扩散,这将导致很难扩展文件格式。
  • 你可以创造一种 ad-hoc 方法,将数据项编码为一个字符串——比如将 4 个整数编码为 12:3:-23:67。虽然它需要编写一次性的编码和解码代码且解码需要耗费一点运行时成本,但这是一种简单灵活的方法。这最适合编码非常简单的数据。
  • 序列化数据为 XML。这种方法是非常吸引人的,因为 XML 是一种适合人阅读的格式,并且有为许多语言开发的库。如果你想与其他程序和项目共享数据,这可能是一种不错的选择。然而,众所周知,XML 是空间密集型的,且在编码和解码时,它对程序会造成巨大的性能损失。同时,使用 XML DOM 树被认为比操作一个类的简单字段更加复杂。

Protocol buffers 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 Protocol buffers,你需要写一个 .proto 说明,用于描述你所希望存储的数据结构。利用 .proto 文件,protocol buffer 编译器可以创建一个类,用于实现对高效的二进制格式的 protocol buffer 数据的自动化编码和解码。产生的类提供了构造 protocol buffer 的字段的 getters 和 setters,并且作为一个单元来处理读写 protocol buffer 的细节。重要的是,protocol buffer 格式支持格式的扩展,代码仍然可以读取以旧格式编码的数据。

安装:

https://github.com/protocolbuffers/protobuf/blob/master/src/README.md

.proto的文件的源代码:

// [START declaration]
syntax = "proto3";
package tutorial; import "google/protobuf/timestamp.proto";
// [END declaration] // [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3; enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
} message PhoneNumber {
string number = 1;
PhoneType type = 2;
} repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
} // Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]

这里,我们对.proto文件所使用的语法进行简单讲解。

1)protobuf使用的.proto文件以包声明开始,包声明和C++中的namespace对应,在某个包声明中定义的消息,会出现在对应的namespace命名空间中。import语句用来导入其他.proto文件中的消息定义,这样就可以在多个.proto文件中定义消息,然后关联使用了。

2)然后,你需要定义消息结构。一个消息包括多个带类型的成员。protobuf有许多标准的简单数据类型,包括bool, int32, float,double以及string, protobuf自带的.proto文件中也有一些消息结构定义,例如上面出现的google.protobuf.Timestamp。当然,你也可以根据这些类型,进一步构造其他消息,例如上面的Person包含了PhoneNumber消息,AddressBook包含了Person消息。你也可以在其他消息中定义消息类型,例如上面出现在PhoneNUmber在Person中进行定义。你还可以定义enum类型,例如上面的PhoneType,包含MOBILE,HOME和WORK三个可选值。

“=1”, “=2”是用来在二进制编码中标识对应字段的tag。tag在1-15范围内只需要一个byte来编码,而较大的数字需要两个byte来编码,所以对于常用的那些字段,可以使用1-15范围内的tag。

另外,每一个tag可以使用如下修饰符修饰:

(1)singular: 表示这个字段可以有一个,也可以没有。如果没有的话,在编码的时候,不会占用空间。

(2)repeated: 表示这个字段会重复0次或者更多次,这个字段里的值会按照顺序编码。

  1. 定义完了.proto文件,下一步就是编译这个proto文件,我们假设这个proto文件名为addressbook.proto。为了编译这个文件,运行如下的语句:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/address.proto

其中-I指定proto文件所在的位置,$DST_DIR指定生成文件所在的位置,这里--cpp_out表示生成文件为C++文件,生成目录在$DST_DIR,$SRC_DIR/addressbook.proto。

如果你在proto所在文件调用上述命令,可以简写如下:

protoc --cpp_out=. addressbook.proto

调用上述命令,生成的文件为addressbook.pb.h和addressbook.pb.cc。可以推测,对于xxx.proto,生成文件应该为xxx.pb.h和xxx.pb.cc。

做成pb格式的文件:

#include <ctime>
#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string> #include "addressbook.pb.h" using namespace std; using google::protobuf::util::TimeUtil; // This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n'); cout << "Enter name: ";
getline(cin, *person->mutable_name()); cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
} while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
} tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number); cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
*person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
} // Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
} tutorial::AddressBook address_book; {
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
} // Add an address.
PromptForAddress(address_book.add_people()); {
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
} // Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary(); return 0;
}

解析pb文件

#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string> #include "addressbook.pb.h" using namespace std; using google::protobuf::util::TimeUtil; // Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.people_size(); i++) {
const tutorial::Person& person = address_book.people(i); cout << "Person ID: " << person.id() << endl;
cout << " Name: " << person.name() << endl;
if (person.email() != "") {
cout << " E-mail address: " << person.email() << endl;
} for (int j = 0; j < person.phones_size(); j++) {
const tutorial::Person::PhoneNumber& phone_number = person.phones(j); switch (phone_number.type()) {
case tutorial::Person::MOBILE:
cout << " Mobile phone #: ";
break;
case tutorial::Person::HOME:
cout << " Home phone #: ";
break;
case tutorial::Person::WORK:
cout << " Work phone #: ";
break;
default:
cout << " Unknown phone #: ";
break;
}
cout << phone_number.number() << endl;
}
if (person.has_last_updated()) {
cout << " Updated: " << TimeUtil::ToString(person.last_updated()) << endl;
}
}
} // Main function: Reads the entire address book from a file and prints all
// the information inside.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
} tutorial::AddressBook address_book; {
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
} ListPeople(address_book); // Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary(); return 0;
}

执行命令:

$ g++ addressbook.pb.cc write.cpp -o write `pkg-config --cflags --libs protobuf`

关于命令pkg-config的用法:https://blog.csdn.net/luotuo44/article/details/24836901

  • --cflags:相当于【-I】(大写的i)属性
  • --libs:相当于【-L】 和【-l】(小写的L)属性

pkg-config能够省略编译时的一堆参数

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

protocol buffers 使用方法的更多相关文章

  1. Google Protocol Buffers 快速入门(带生成C#源码的方法)

    Google Protocol Buffers是google出品的一个协议生成工具,特点就是跨平台,效率高,速度快,对我们自己的程序定义和使用私有协议很有帮助. Protocol Buffers入门: ...

  2. Xml,Json,Hessian,Protocol Buffers序列化对比

    简介 这篇博客主要对Xml,Json,Hessian,Protocol Buffers的序列化和反序列化性能进行对比,Xml和Json的基本概念就不说了. Hessian:Hessian是一个轻量级的 ...

  3. protocol buffers的使用示例[z]

    [http://blog.csdn.net/zhu_xun/article/details/19397081] protocol buffers的使用示例 如果不了解protocol buffers, ...

  4. 理解netty对protocol buffers的编码解码

    一,netty+protocol buffers简要说明 Netty是业界最流行的NIO框架之一优点:1)API使用简单,开发门槛低:2)功能强大,预置了多种编解码功能,支持多种主流协议:3)定制能力 ...

  5. Protocol Buffers(Protobuf)开发者指南---概览

    Protocol Buffers(Protobuf)开发者指南---概览 欢迎来到protocol buffers的开发者指南文档,protocol buffers是一个与编程语言无关‘.系统平台无关 ...

  6. Google Protocol Buffers简介

    什么是 protocol buffers ? Protocol buffers 是一种灵活.高效的序列化结构数据的自动机制--想想XML,但是它更小,更快,更简单.你只需要把你需要怎样结构化你的数据定 ...

  7. 使用 Protocol Buffers 代替 JSON 的五个原因

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...

  8. Protocol Buffers编码详解,例子,图解

    Protocol Buffers编码详解,例子,图解 本文不是让你掌握protobuf的使用,而是以超级细致的例子的方式分析protobuf的编码设计.通过此文你可以了解protobuf的数据压缩能力 ...

  9. 如何在 PHP 中处理 Protocol Buffers 数据

    Protocol Buffers是谷歌定义的一种跨语言.跨平台.可扩展的数据传输及存储的协议,因为将字段协议分别放在传输两端,传输数据中只包含数据本身,不需要包含字段说明,所以传输数据量小,解析效率高 ...

随机推荐

  1. JavaScript---动态加载script和style样式

    一个网页里面的内容理解为一个XML或者说网页本身也就是一个XML文档,XML文档都有很特殊的象征:"标签"也叫"节点". 一个基本的网页格式 <!DOCT ...

  2. 模块二之序列化模块以及collections模块

    模块二之序列化模块以及collections模块 一.序列化模块 json模块 ''' 序列化:将python或其他语言的数据类型转换成字符串类型 json模块: 是一个序列化模块. json: 是一 ...

  3. 网络编程~~~ socket(套字节)

    一 socket (套接字) socket处于应用层与传输层之间,提供了一些简单的接口,避免与操作系统之间的对接,省去了相当繁琐复杂的操作. socket在python中属于一个模块,通过调用模块中已 ...

  4. MySQL之架构简单分析

    上图为MySQL的简易架构图,给您有一个大概的概念,下面我将为您进行进一步的分析. 连接器: 当连接MySQL数据库时,等待的将是MySQL服务端的连接器:连接器的职责是和客户端建立连接.获取权限.维 ...

  5. Linux-3.14.12内存管理笔记【建立内核页表(2)】-低端内存的建立

    前面的前奏已经分析介绍了建立内核页表相关变量的设置准备,接下来转入正题分析内核页表的建立. 建立内核页表的关键函数init_mem_mapping(): [file:/arch/x86/mm/init ...

  6. Octave-CostFunction

    假设 X= [1 1;1 2;1 3] y = [1;2;3] theta = [0;0] costFunction J : function J = costFunction(X,y,theta) ...

  7. Android程序中,内嵌ELF可执行文件-- Android开发C语言混合编程总结

    前言 都知道的,Android基于Linux系统,然后覆盖了一层由Java虚拟机为核心的壳系统.跟一般常见的Linux+Java系统不同的,是其中有对硬件驱动进行支持,以避开GPL开源协议限制的HAL ...

  8. Scrum会议(第十一周)

    十一周会议 1.任务分配: github相关代码 2.会议记录: 总结并阐述了自己在上周存在的问题以及解决了自己把自己的issue发在了自己的github,并没有把问题发在我们项目小组的项目的issu ...

  9. 给用户提供就医帮助的安卓APP

    经过我们的小组的成员讨论,我们确定了我们小组的项目,即是一款给用户提供就医帮助的安卓APP. 项目计划及功能:计划两个月内团队成员共同开发完成此款APP,此款APP提供预约挂号,名医名院咨询, 就医导 ...

  10. C语言程序设计100例之(5):分解质因数

    例5    分解质因数 题目描述 将一个正整数分解质因数.例如:输入90,输出 90=2*3*3*5. 输入 输入数据包含多行,每行是一个正整数n (1<n <100000) . 输出 对 ...