本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循署名-非商业用途-保持一致的创作共用协议.

Google Protocol Buffers简称 Protobuf, 是Google公司内部的混合语言数据标准. 它提供一种轻量, 高效的结构化数据存储结构.

简介

为什么学习protobuf?

想通过protobuf的序列化来做一个C++的微型RPC框架.
本文主要是学习protobuf的使用, 大量参考官方文档.

为什么要使用protobuf?

  1. 因为protobuf是谷歌出的, 性能不过, 重要是的我是谷歌脑残粉(逃
  2. 官方文档中提到一些protobuf的优点, protobuf灵活高效的结构化数据存储格式. 方便用于序列化, 适合做RPC的数据交换.
  3. 相比XML, protobuf比 XML 更小、更快、更简单. 仅需要写一个*.proto文件描述需要的数据结构, protobuf会帮助你实现相关类和方法(自动化多好!).
  4. 目前提供C++, Java, Python, Go, C#等多种语言的API

安装

神奇Mac版homebrew帮你解决一切问题

1
2
3
4
5
6
7
# 安装最新的protobuf
$ brew install protobuf
# 验证protobuf是否安装成功
$ protoc --version
# 在我电脑上的输出
libprotoc 3.0.0

学习笔记

通过一个简单的官方例子来学习protobuf

我们首先定义一个addressbook.proto, 通过message来定义需要序列化的数据结构, message内包含许多key-value对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# addressbook.proto
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
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;
}
message AddressBook {
repeated Person person = 1;
}

ps:

  1. message中有多种类型, bool, int32, float, double, ,string and enum以及内嵌的message
  2. message内可以定义optional、required、repeated字段

定义好*.proto文件后, 我们通过protobuf提供的protoc命令行工具来自动生成相关数据结构源码

1
2
3
4
5
// 编译.proto文件
$ protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
// 范例, 源目录为当前文件, 输出路径为当前文件
$ protoc -I=./ --cpp_out=./ ./addressbook.proto
  • -I指明源文件所在目录
  • --cpp_out指向想要输出的文件路径
  • 最后一个参数为.proto所在的路径

使用protobuf

生成我们自定义的类后, 我们开始尝试使用生成的文件. add_person.cpp用于从用户的输入中, 创建一个Person并初始化一个Person对象, 并将该对象通过二进制的形式写入给定文件中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 代码出自官方文档
// add_person.cpp
//
// Created by Andrew_liu on 16/4/17.
//
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// 基于用户输出填充Person message
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: ";
string number;
getline(cin, number);
if(number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phone();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone(type: mobile/home/work): ";
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;
}
}
}
int main(int argc, char* argv[]) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook 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_person());
{
// write 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;
}
}
// delete all global object allocated by libprotobuf
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

list_person.cpp通过给定的文件名读取Person对象, 并将每个Person对象输出.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// list_person.cpp
// Created by Andrew_liu on 16/4/17.
//
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.person_size(); i++) {
const tutorial::Person& person = address_book.person(i);
cout << "Person ID: " << person.id() << endl;
cout << "Name: " << person.name() << endl;
if (person.has_email()) {
cout << "E-mail address: " << person.email() << endl;
}
for (int j = 0; j < person.phone_size(); ++j) {
const tutorial::Person::PhoneNumber& phone_number = person.phone(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 #: ";
}
cout << phone_number.number() << endl;
}
}
}
int main(int argc, char* argv[]) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook 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);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}

完成后, 我们分别编译两个源文件, 编译命令如下:

1
2
3
4
5
# 编译add_person.cpp, 生成writer可执行文件
$ g++ -o writer add_person.cpp addressbook.pb.cc `pkg-config --cflags --libs protobuf`
# 编译list_person.cpp, 生成reader可执行文件
g++ -o reader list_person.cpp addressbook.pb.cc `pkg-config --cflags --libs protobuf`

图片为执行writerreader的结果

实现RPC

1
2
3
4
5
6
7
8
9
10
package news;
message NewsRequest {
required string message = 1;
};
message NewsResponse {
required string response = 1;
};
service NewsService {
rpc News(NewsRequest) returns (NewsResponse);
};

参考链接