Google Protocol Buffers 學習筆記

Google Protocol Buffers 學習ios

Protocol BuffersPB)是一個用於序列化結構化數據的機制,是谷歌的一個開源項目,在github上有源代碼,也有發行版。PBXML類似,XML序列化的時候速度是能夠接受的,可是XML解析數據的時候很是慢。然而Protocol Buffers則是很是輕量,速度也很快。c++

 

咱們學習Protocol Buffers,直接經過實現一個Protocol Buffers小項目來實驗,得出其源代碼,再分析編碼技術。git

安裝完Protocol Buffers以後(安裝過程略去,咱們選擇的是Github上面的源代碼c++版,而不是發行版,有利於咱們學習編碼技術),咱們開始正式實驗。github

 

關於PB的編碼 shell

由官方文檔,PB先用variant編碼整形數據,而後再進行操做json

咱們打開編譯好的源代碼咱們找到了這樣一條在序列化的時候所調用的函數vim

  if (has_sid()) {
    ::google::protobuf::internal::WireFormatLite::WriteInt32(2, this->sid(), output);
  }


使用gdb調試,嘗試進入函數體內部,發現其調用:函數

src/google/protobuf/message.cc:174 的一個函數學習

bool Message::SerializeToOstream(ostream* output) const測試

最後找到了編碼的函數:

src/google/protobuf/coded_stream.cc

inline uint8* CodedOutputStream::WriteVarint32FallbackToArrayInline(
    uint32 value, uint8* target) {
  target[0] = static_cast<uint8>(value | 0x80);
  if (value >= (1 << 7)) {
    target[1] = static_cast<uint8>((value >>  7) | 0x80);
    if (value >= (1 << 14)) {
      target[2] = static_cast<uint8>((value >> 14) | 0x80);
      if (value >= (1 << 21)) {
        target[3] = static_cast<uint8>((value >> 21) | 0x80);
        if (value >= (1 << 28)) {
          target[4] = static_cast<uint8>(value >> 28);
          return target + 5;
        } else {
          target[3] &= 0x7F;
          return target + 4;
        }
      } else {
        target[2] &= 0x7F;
        return target + 3;
      }
    } else {
      target[1] &= 0x7F;
      return target + 2;
    }
  } else {
    target[0] &= 0x7F;
    return target + 1;
  }
}

@font-face {   font-family: "Times New Roman"; }@font-face {   font-family: "宋體"; }@font-face {   font-family: "Liberation Serif"; }@font-face {   font-family: "Mangal"; }p.p0 { margin: 0; font-size: 16px; font-family: "Liberation Serif"; }div.Section0 { page: Section0; }

variant編碼中第一個字節的高位msb1表示下一個字節還有有效數據,msb0表示該字節中的後7爲是最後一組有效數字。踢掉最高位後的有效位組成真正的數字。

 

如數據value = 123456 運行上述程序的過程:

12345D = 3039H

target[0] = u8(3039H | 80H)       B9

30B9H > 80H

target[1] = 30B9H >> 7        60H

61H < 8000H

target[1] &= 0x7F   60H



結果應該是:B961H = 1011 1001 0110 0000 B

低位是的元數據位是1,表示後面還有8位的數據,依次下去

按照規則解析過來,恰好就是12345D

int32的編碼

用上述方法使用gdb調試程序,獲得函數入口

1、 計算長度1 + Int32Size(value);

(gdb) 

google::protobuf::io::CodedOutputStream::VarintSize32 (value=)

    at /usr/local/include/google/protobuf/io/coded_stream.h:1108

2、 調用這個函數,將值寫入新的空間之中去

(gdb) step

google::protobuf::internal::WireFormatLite::Int32Size (value=)

    at /usr/local/include/google/protobuf/wire_format_lite_inl.h:797

存儲的時候分爲tagvalue兩塊。tag部分由公式field_number << 3 | WITETYPE_VARIANT給出,

value部分則由用戶給的值的variant編碼構成

 

String類型的編碼

一樣須要使用variant編碼,string的要求是UTF8的編碼的。

先計算計算長度公式 1 + variant(stringLength)+stringLength

編碼後的格式爲tag+length(variant int)+value

tage爲公式field_number << 3 | WITETYPE_VARIANT給出,length則是長度的variant編碼,value爲用戶字符串內容

函數:

(gdb) 

google::protobuf::internal::WireFormat::VerifyUTF8StringFallback (

    data=0x613118 "ye jiaqi", size=8, 

    op=google::protobuf::internal::WireFormat::SERIALIZE, 

    field_name=0x407dc8 "Lab.Student.name")

    at google/protobuf/wire_format.cc:1089

1089                                           const char* field_name) {

 

下面進行實操:

 

定義一個Protocol Buffers

package Lab;
message Student{
required string name = 1;
required int32 sid = 2;
required int32 age = 3;
enum genderType {
FEMALE = 0;
MALE = 1;
}

required genderType gender = 4;
optional string phone = 5;
optional string email = 6;
}


而後使用protoc將其編譯爲c++

 寫一個簡單的腳本測試這個類


#include <iostream>
#include <fstream>
#include <string>
#include "student.pb.h"
 
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::fstream;
using std::ios;
 
void setStudent(Lab::Student * student) {
  cout << "Enter the student's name" << endl;;
  string name;
  getline(cin, name);
  student->set_name(name);
 
  cout << "Enter the student's id" << endl;
  int id;
  cin >> id;
  student->set_sid(id);
 
  cout << "Enter the student's age" << endl;
  int age;
  cin >> age;
  student->set_age(age);
  
  cout << "Enter the student's gender 'male' or 'female'" << endl;
  string gender;
  cin.ignore(256, '\n');
  getline(cin, gender);
  if(gender == "male") {
    student->set_gender(Lab::Student::MALE);
  } else if (gender == "female") {
    student->set_gender(Lab::Student::FEMALE);
  } else {
    cerr << "no such type set default(male)" << endl;
    student->set_gender(Lab::Student::MALE);
  }
  
  cout << "Enter the student's phone" << endl;
  string phone;
  getline(cin, phone);
  student->set_phone(phone);
 
  cout << "Enter the student's email" << endl;
  string email;
  getline(cin, email);
  student->set_email(email);
  
}
 
int main(int argc, char * argv[]) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;
  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }
 
  // scope output
  {
    Lab::Student student;
    setStudent(&student);
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!student.SerializeToOstream(&output)) {
      cerr << "Failed to write address book." << endl;
      return -1;
    } else {
      string output;
      student.SerializeToString(&output);
      //cout << output << endl;
    }
  }
 
  // scope input
  {
    Lab::Student student;
    fstream input(argv[1], ios::in | ios::binary);
    if (!input) {
      cout << argv[1] << ": File not found.  Creating a new file." << endl;
    } else if (!student.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    } else {
      cout.setf(ios::left);
      cout.fill(' ');
      cout << "student's id: \t\t" << student.sid() << endl;
      cout << "student's name: \t" << student.name() << endl;
      cout << "student's age: \t\t" << student.age() << endl;
      cout << "student's gender: \t" << student.gender() << endl;
      cout << "student's phone: \t" << student.phone() << endl;
      cout << "student's email: \t" << student.email() << endl;
    }
  }
  
  google::protobuf::ShutdownProtobufLibrary();
 
  return 0;
}


<info.input

ye jiaqi

13331314

20

male

18819473230

423093883@qq.com


使用以下命令來進行編譯,爲了方便,定義一個makefile:

main:proto student.pb.h test.cpp
g++ test.cpp student.pb.cc student.pb.h -lprotobuf -o student -W -g
./student test.txt <info.input
proto:student.proto
protoc -I=./ --cpp_out=./ ./student.proto
clean:
rm -f *.out *.h.gch
rm -f student
rm -f *.pb.h
rm -f *.pb.cc
rm -f *.txt


運行結果以下

而後咱們用vim 的二進制方式打開存儲的文件,分析其中部分的結構

開始的前兩位OAprotobuf存儲文件時候預約義的兩位,結束時候也是

 

字符串:「ye jiaqi」 : 08則是 由tag = 1 的預約義的name protobuf枚舉類型中的2獲得, 1 << 3 | 2 = 08 H, 後面的是utf8編碼的字符串內容,能夠被vim識別

 

INT32 「13331314」: 按照咱們以前的編碼方式

2 >> 3 | 0 = 10H

13331314CD6B72H)使用variant方式編碼獲得的是F2D6AD06H, 跟咱們的編碼方式徹底一致

 

總結

protobuf是一個很好的輕量級的編碼方式,比XML要好。然而json也是一種輕量級的數據傳輸協議。查了一下資料,json在許多報文方面仍是不太行,protobuf不爲一種很好的選擇。但是protobuf文件的可讀性約等於0,這個缺點實在有些。。。。

相關文章
相關標籤/搜索