protobuf序列化编码实例分析
这几天把google protobuf官方文档通读了一遍, 总觉得对message序列化后的内容理解的不够透彻,因此动手操作一遍,分析一下message序列化后的内容。程序代码是官网的。
- proto文件内容
// file addressbook.proto
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
HOME = 0;
MOBILE = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
service SearchService {
rpc Search (Person) returns (Person);
}
- 序列化写入程序
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
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;
}
}
}
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;
}
}
PromptForAddress(address_book.add_person());
{
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
- 序列化读取程序
#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;
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;
}
}
}
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;
}
执行写入程序:
root@instance-8alx1qc6-1:~/github/learning/protobuf/output# ./writer book
book: File not found. Creating a new file.
Enter person ID number: 123
Enter name: ck
Enter email address (blank for none): ck@f.com
Enter a phone number (or leave blank to finish): 11
Is this a mobile, home, or work phone?
Unknown phone type. Using default.
Enter a phone number (or leave blank to finish):
将message序列化到book文件里面,接下来我们看下book文件里面的内容:
root@instance-8alx1qc6-1:~/github/learning/protobuf/output# xxd -g 1 book
0000000: 0a 16 0a 02 63 6b 10 7b 1a 08 63 6b 40 66 2e 63 ....ck.{..ck@f.c
0000010: 6f 6d 22 04 0a 02 31 31 om"...11
The available wire types are as follows:
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (deprecated) |
4 | End group | groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
我们逐个字节分析
0a: flag=1, wire type=2;
16: 22(也就是后面的所有内容长度)
0a: flag=1, wire type=2
02: 2(也就是后面字符串的长度为2)
63: 99,也就是c
6b: 107,也就是k
10: flag=2, wire type=0,也就是varint类型
7b: 123, 对应上id=123;
1a: flag=3, wire type=2
08: 8(也就是后面字符串的长度为8)
63 6b 40 66 2e 63 6f 6d: ck@f.com
22: flag=4, wire type=2
04: 4(后面内容的长度为4,也就是PhoneNumber的长度为4)
0a: flag=1, wire type=2;
02: 2(后面字符串的长度为2)
31 31: 11
在对字节和message各个字段的时候,一开始有个疑惑,为什么序列化的开头开头会有0a 16
,想了好久,protobuf官网上也没找到相应的说明(或者我漏看了?),后来才发现0x16=22刚好是后面的字节总数,这是因为Person
作为AddressBook
的一个变量,作为变量的Message,外层将它作为Length-delimited
类型看待。