Protocol Buffers 详解1
欢迎放我的博客空间:
https://satisfie.github.io/
Reference:
下面这个翻译已经翻译的很详细
Protocol Buffers 是一种与语言无关,平台无关,可扩展的轻便高效的数据存储格式,用于结构化数据的序列化,适用于通信协议,数据存储等领域,包括C++,Java,Python,C#等多种语言支持。
优点
- 提供灵活高效的结构化数据序列化
- 类似与XML,但是更小,更快,更简单
- 在proto文件中定义数据结构后,可以用Protobuf编译器编译成各种目标语言
Proto 文件的书写
定义 Message 类型
message SearchRequest{
required string query=1;
optional int32 page_number=2;
optional int32 result_per_page=3;
}
如下的message类型定了3种特定的name/value字段
字段的类型
- 标量类型: 包括double,float,int32,int64,uint32,uint64,string,bool,bytes等
- 枚举类型:例如
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [default = UNIVERSAL];
}
- 其他类型
字段的tag
数字tag,用于标识该字段。1-15可以用1个byte编码,从16到2047使用两个byte编码,所以常用的字段应该用1-15打上tag。
字段的规则:
- required: 确定有一个
- optional: 可以有0个或1个,不能多于1个
- repeated: 字段可以重复任意多次(包括0次),定义该字段时在后面加上[packed=true]用以更有效的编码,如
repeated int32 samples = 4 [packed=true];
- reserved 保留字段,保证这个字段不会再次被使用
从Proto 文件生成
用protocol buffer编译器编译proto文件,生成对应语言的代码。包括getting和setting对应的字段,以及序列化流和消息的反序列化。
- 对于C++: 每个proto文件生成一个.h和.cc文件,每个消息类型生成对应的类
- 对于Java:编译器为每个消息生成一个.java文件,外加一个特殊的Builder类来生成消息实例
- 对于Python: 一点点不同 —– Python编译器生成有一个静态的对每个消息的描述器的模块。然后,用一个元类在运行时创建必要的Python数据访问类。
- 对于Go:编译器对文件中的每个消息生成一个.pb.go文件。
使用其它的Message类型
写在一个proto文件中可以直接用
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
Import
如果需要使用的message定义在其它的proto文件中,可以使用import
import "myproject/other_protos.proto";
默认情况下你只能使用直接导入的文件中的定义。然而有的时候你需要将一个文件从一个路径移动到另一个路径的时候,与其将所有的引用这个文件的地方都更新到新的路径,不如在原来的路径上留下一个假的文件,使用import public来指向新的路径。import public语句可以将它导入的文件简介传递给导入本文减的文件。比如 :
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
在命令行中试用-I/–proto_path来指定一系列的编译器搜索路径,如果这个参数没有被设置,那么默认在命令执行的路径查找。通常情况下使用-I/–proto_path来指定到你项目的根目录,然后使用完整的路径来导入所需的文件。
内嵌形式
你可以在一个消息中定义并使用其他消息类型,比如下面的例子 —— Result消息是在SearchResponse中定义的
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
如果你打算在这个消息的父消息之外重用这个消息的话,你可以这样引用它
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}
你想嵌套多深就嵌套多深,没有限制
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
required int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
required int32 ival = 1;
optional bool booly = 2;
}
}
}
先写到这,有需要继续。先实践
创建自己的Protocol格式
定义个proto文件,添加message以及各个字段。如以下addressbook.proto
syntax= "proto2";
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;
}
编译 .proto 文件
写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。
假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令就可以生存对应的addressbook.pb.h和addressbook.pb.cc
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
其中package 后面的字段会生成对应的域名空间 上面的例子对应namespace 是tutorial。
则对应的h文件中,会有下列的接口,实际的会更多。
// name
inline bool has_name() const;
inline void clear_name();
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline ::std::string* mutable_name();
// id
inline bool has_id() const;
inline void clear_id();
inline int32_t id() const;
inline void set_id(int32_t value);
// email
inline bool has_email() const;
inline void clear_email();
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline ::std::string* mutable_email();
// phone
inline int phone_size() const;
inline void clear_phone();
inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const;
inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();
inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
inline ::tutorial::Person_PhoneNumber* add_phone();
写以下 Application (writeMain.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// 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_phone();
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;
}
}
}
// 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_person());
{
// 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;
}
g++ -o write addressbook.pb.cc writeMain.cpp -lprotobuf 进行编译
编译时需要-lprotobuf
写Read Message 的Application(readmain.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// 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.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 #: ";
break;
}
cout << phone_number.number() << 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++ -o read addressbook.pb.cc readMain.cpp -lprotobuf
运行 ./write Address写入信息
运行./read Address读出信息