Google的Protobuf協議分析

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.

相關文章
相關標籤/搜索