protobuf和thrift相似,也是一個序列化的協議實現,簡稱PB(下文出現的PB表明protobuf)。
html
Github:https://github.com/google/protobufios
上圖,說明一下protobuf協議。c++
PB以「1-5個字節」的編號和類型開頭,格式:編號左移3位和類型取或獲得。git
編號是什麼?github
編號就是 定義的proto文件中各個字段的編號。算法
如:bash
類型是什麼?app
類型就是 定義的proto文件中各個字段類型,使用3位表示類型,能夠表示0到7,共8種類型,PB類型只用了0,1,2,3,4,5這6種類型。測試
詳細描述參考以下表格:ui
類型 | 描述 | 使用於哪些類型 |
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 |
看到圖和表格時是否是有不少迷惑的地方?
1. 爲何編號類型,32位數值,32位負載長度數值都佔用 「1-5個字節」?
2. 爲何64爲的數值佔用「1-10個字節」?
3. Varint是什麼?
4. ZigZag是什麼?
解決這些問題的關鍵:PB對數值進行壓縮,壓縮算法就是Varint,負數進行zigzag編碼後再作varint編碼,什麼是Varint數值壓縮?
爲了詳細的瞭解varint的編碼,能夠參考個人另外一篇文章 Thrift TCompactProtocol協議分析的varint介紹部分。
看完連接中描述的varint編碼和zigzag編碼後,繼續分析。
編寫一個demo分析一下PB協議。
1. 編寫proto接口文件
package demo; enum AuctionType { FIRST_PRICE = 1; SECOND_PRICE = 2; FIXED_PRICE = 3; } message VarintMsg { required int32 argI32 = 1; required int64 argI64 = 2; required uint32 argUI32 = 3; required uint64 argUI64 = 4; required sint32 argSI32 = 5; required sint64 argSI64 = 6; repeated bool argBool = 7; optional AuctionType argEnum = 8; } message Bit64 { required fixed64 argFixed64 = 1; required sfixed64 argSFixed64 = 2; required double argDouble = 3; } message Bit32 { required fixed32 argFixed32 = 1; required sfixed32 argSFixed32 = 2; required float argFloat = 3; } message LenPayload { repeated string argStrList = 1; optional VarintMsg argVarintMsg = 2; optional Bit64 argBit64 = 3; optional Bit32 argBit32 = 4; }
2. 編寫測試代碼
/* ** Copyright (C) 2014 Wang Yaofu ** All rights reserved. ** **Description: The source file of demo. */ #include "demo.pb.h" #include <string> #include <iostream> #include <fstream> using namespace std; int appendFile(const string& file, const char* dataPtr, int len) { std::ofstream ofs(file, std::ofstream::app | std::ofstream::binary); if (ofs.is_open() && ofs.good()) { ofs.write(dataPtr, len); } return len; } int main(int argc, char *argv[]) { demo::VarintMsg* varintMsg = new demo::VarintMsg(); varintMsg->set_argi32(0x41); varintMsg->set_argi64(0x12345678); varintMsg->set_argui32(0x332211); varintMsg->set_argui64(0x998877); varintMsg->set_argsi32(-100); varintMsg->set_argsi64(-200); varintMsg->add_argbool(true); varintMsg->add_argbool(false); varintMsg->set_argenum(demo::SECOND_PRICE); demo::Bit64* bit64 = new demo::Bit64(); bit64->set_argfixed64(0x123456); bit64->set_argsfixed64(-100); bit64->set_argdouble(3.1415926); demo::Bit32* bit32 = new demo::Bit32(); bit32->set_argfixed32(0x1234); bit32->set_argsfixed32(-10); bit32->set_argfloat(3.1415); demo::LenPayload* lenPayload = new demo::LenPayload(); lenPayload->add_argstrlist("String 1."); lenPayload->add_argstrlist("String 2."); lenPayload->set_allocated_argvarintmsg(varintMsg); lenPayload->set_allocated_argbit64(bit64); lenPayload->set_allocated_argbit32(bit32); std::string content; lenPayload->SerializeToString(&content); appendFile("pb.bin", content.data(), content.length()); delete lenPayload; return 0; }
3. 編寫Makefile
CXX = g++ -g -std=c++11 PB_HOME = ./tools/protobuf-2.6.1/inbin/ PROTOC = LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(PB_HOME)/lib $(PB_HOME)/bin/protoc CXXFLAGS = -I$(PB_HOME)/include -I. LDFLAGS = -L$(PB_HOME)/lib -lprotobuf all: demo.pb.h demo demo.pb.h : $(PB_HOME)/bin/protoc --cpp_out=. ./demo.proto demo : ${CXX} ${CXXFLAGS} -o demo demo.cpp ${LDFLAGS} clean: rm -rf demo *.pb.*
4. 編譯後運行demo,獲得二進制文件pb.bin
5. 按字節分析
5.1.消息message LenPayload的第一個字段分析:
repeated string argStrList = 1;
字節 0a 表示編號和類型:
編號爲1,類型爲2,1 << 3 | 2 = 1000 | 0010 = 1010 = 8+2 = 10 = 0a
字節 09 表示負載信息的長度爲9:
字節:"53 74 72 69 6e 67 20 31 2e" 爲 "String 1. ",長度正好爲9.
字段argStrList是可重複的,因此緊接着的字節 0a 09表示編號類型和長度。
字節:"53 74 72 69 6e 67 20 32 2e" 爲 "String 2. "。
對應代碼:
lenPayload->add_argstrlist("String 1."); lenPayload->add_argstrlist("String 2.");
5.2. 消息message LenPayload的第二個字段分析:
optional VarintMsg argVarintMsg = 2;
字節:"12 1e 08 41 10 f8 ac d1 91 01 18 91 c4 cc 01 20 f7 90 e6 04 28 c7 01 30 8f 03 38 01 38 00 40 02"
字節 12 表示編號和類型:
編號爲2,類型爲2,2 << 3 | 2 = 10000 | 0010 = 10010 = 16+2 = 18 = 0x12
字節 1e 表示負載信息的長度爲30.
5.2.1. message VarintMsg消息分析
required int32 argI32 = 1;
varintMsg->set_argi32(0x41);
08 41
字節08 表示編號和類型:
編號爲1,類型爲0,1 << 3 | 0 = 1000 | 0000 = 1000 = 8 = 0x08
字節 41 表示值爲 0x41.
required int64 argI64 = 2;
varintMsg->set_argi64(0x12345678);
10 f8 ac d1 91 01
字節10 表示編號和類型:
編號爲2,類型爲0,2 << 3 | 0 = 10000 | 0000 = 10000 = 16 = 0x10
字節 f8 ac d1 91 01二進制表示值爲
1111 1000, 1010 1100, 1101 0001, 1001 0001, 0000 0001
小端轉本地爲 0000 0001, 1001 0001, 1101 0001, 1010 1100, 1111 1000
去掉紅色的1,varint恢復爲 0001 0010, 0011 0100, 0101 0110, 0111 1000 表示爲16進制就是 0x12345678
required uint32 argUI32 = 3;
varintMsg->set_argui32(0x332211);
18 91 c4 cc 01
字節18 表示編號和類型:
編號爲3,類型爲0,3 << 3 | 0 = 11000 | 0000 = 11000 = 16 + 8 = 24 = 0x18
字節91 c4 cc 01二進制爲 1001 0001, 1100 0100, 1100 1100, 0000 0001
小端轉本地爲 0000 0001, 1100 1100, 1100 0100 , 1001 0001
去掉紅色的1,varint恢復爲 0011 0011, 0010 0010, 0001 0001 表示爲16進制就是 0x332211
required uint64 argUI64 = 4;
varintMsg->set_argui64(0x998877);
20 f7 90 e6 04
字節20 表示編號和類型:
編號爲4,類型爲0,4 << 3 | 0 = 100000 | 0000 = 100000 = 32 = 0x20
字節 f7 90 e6 04二進制爲 1111 0111, 1001 0000, 1110 0110, 0000 0100
小端轉本地爲 0000 0100, 1110 0110, 1001 0000, 1111 0111
去掉紅色的1,varint恢復爲 1001 1001,1000 1000, 0111 0111 表示爲16進制就是 0x998877
required sint32 argSI32 = 5;
varintMsg->set_argsi32(-100);
28 c7 01
字節28 表示編號和類型:
編號爲5,類型爲0,5 << 3 | 0 = 101000 | 0000 = 101000 = 32 + 8 = 40 = 0x28
字節 c7 01二進制表示爲 1100 0111, 0000 0001
小端轉爲本地爲 0000 0001, 1100 0111
去掉紅色的1,varint恢復爲 1100 0111 = 199 = -100 * -2 - 1,正好是-100作zigzag後varint壓縮獲得的值。
required sint64 argSI64 = 6;
varintMsg->set_argsi64(-200);
30 8f 03
字節30 表示編號和類型:
編號爲6,類型爲0,6 << 3 | 0 = 110000 | 0000 = 110000 = 32 + 16 = 48 = 0x30
字節 8f 03二進制表示爲1000 1111, 0000 0011
小端轉本地爲 0000 0011, 1000 1111
去掉紅色的1,varint恢復爲11000 1111 = 399 = -200 * -2 -1,正好是-200作zigzag後varint壓縮獲得的值。
repeated bool argBool = 7;
varintMsg->add_argbool(true);
38 01
字節38 表示編號和類型:
編號爲7,類型爲0,7 << 3 | 0 = 111000 | 0000 = 111000 = 32 + 16 + 8 = 56 = 0x38
字節 01 表示值爲1, 是true.
repeated bool argBool = 7;
varintMsg->add_argbool(false);
38 00
字節38 表示編號和類型:
編號爲7,類型爲0,7 << 3 | 0 = 111000 | 0000 = 111000 = 32 + 16 + 8 = 56 = 0x38
字節 00 表示值爲 0,是false.
optional AuctionType argEnum = 8;
varintMsg->set_argenum(demo::SECOND_PRICE);
40 02
字節40表示編號和類型:
編號爲3,類型爲0,8 << 3 | 0 = 1000000 | 0000 = 1000000 = 64 = 0x40
字節 02 表示值爲 2,是枚舉的值demo::SECOND_PRICE值爲2.
5.3. 消息message LenPayload的第三個字段分析:
optional Bit64 argBit64 = 3;
「1a 1b 09 56 34 12 00 00 00 00 00 11 9c ff ff ff ff ff ff ff 19 4a d8 12 4d fb 21 09 40」
字節 1a 表示編號和類型:
編號爲3,類型爲2,0x1a = 3 << 3 | 2 = 11000 | 0010 = 11010 = 16+8+2 = 26 = 0x1a
字節 1b 表示負載信息的長度爲27。
5.3.1. message Bit64消息分析
required fixed64 argFixed64 = 1;
bit64->set_argfixed64(0x123456);
09 56 34 12 00 00 00 00 00
字節 09 表示編號和類型:
編號爲1,類型爲1,0x0d = 1 << 3 | 1 = 1000 | 0001 = 1001 = 8+1 = 9 = 0x09
接着8個字節表示64位數的負載信息。
"56 34 12 00 00 00 00 00":從小端表示轉成本地表示爲 00 00 00 00 00 12 34 56, 表示 0x123456
required sfixed64 argSFixed64 = 2;
bit64->set_argsfixed64(-100);
11 9c ff ff ff ff ff ff ff
字節 09 表示編號和類型:
編號爲2,類型爲1,0x11 = 2 << 3 | 1 = 10000 | 0001 = 10001 = 16+1 = 17 = 0x11
接着8個字節表示64位數的負載信息。
"9c ff ff ff ff ff ff ff":從小端表示轉成本地表示爲 ff ff ff ff ff ff ff 9c, 表示 -100
required double argDouble = 3;
bit64->set_argdouble(3.1415926);
19 4a d8 12 4d fb 21 09 40
字節 19 表示編號和類型:
編號爲3,類型爲1,0x19 = 1 << 3 | 1 = 11000 | 0001 = 11001 = 16+8+1 = 25 = 0x19
接着8個字節表示64位數的負載信息。
"4a d8 12 4d fb 21 09 40":表示: 3.1415926
5.4. 消息message LenPayload的第四個字段分析:
optional Bit32 argBit32 = 4;
「22 0f 0d 34 12 00 00 15 f6 ff ff ff 1d 56 0e 49 40」
字節 22 表示編號和類型:
編號爲4,類型爲2,0x22 = 4 << 3 | 2 = 100000 | 0010 = 100010 = 32+2 = 34 = 0x22
字節 0f 表示負載信息的長度爲15:
5.4.1. message Bit32消息分析
required fixed32 argFixed32 = 1;
bit32->set_argfixed32(0x1234);
0d 34 12 00 00
字節 0d 表示編號和類型:
編號爲1,類型爲5,0x0d = 1 << 3 | 5 = 1000 | 0101 = 1101 = 8+4+1 = 13 = 0x0d
接着4個字節表示32位數的負載信息。
"34 12 00 00":從小端表示轉成本地表示爲 00 00 12 34, 表示 0x1234
required sfixed32 argSFixed32 = 2;
bit32->set_argsfixed32(-10);
15 f6 ff ff ff 1d
字節 15 表示編號和類型:
編號爲2,類型爲5,0x15 = 1 << 3 | 5 = 10000 | 0101 = 10101 = 16+4+1 = 21 = 0x15
接着4個字節表示32位數的負載信息。
"ff ff ff 1d": 表示 -10
required float argFloat = 3;
bit32->set_argfloat(3.1415);
1d 56 0e 49 40
字節 1d 表示編號和類型:
編號爲3,類型爲5,0x1d = 3 << 3 | 5 = 11000 | 0101 = 11101 = 16+8+4+1 = 29 = 0x1d
接着4個字節表示32位數的負載信息。
"56 0e 49 40": 表示 3.1415
測試代碼:https://github.com/gityf/utils/tree/master/pb_analysis_demo
Done.