【技術類】PB二進制序列化


自從使用protobuf作序列化工具之后,每次面試都問我,為什么用?
很迅速的回答了體積小,解析快。
為什么小,采用了varint的壓縮方式,那你講一下這個壓縮方式,然后emmm…
那為什么解析快,有沒有跟其他做過對比,又是emmm…

test.proto
syntax = "proto2";

message Test
{
	required int32 first = 1;
	optional int32 second = 2;
	optional string third = 3;
}
這里使用C++語言,編譯到當前目錄命令: protoc test.proto --cpp_out=./
產生兩個文件:test.pb.h	test.pb.cc(動態鏈接庫)
#include <iostream>
#include "test.pb.h"
using namespace std;

int main()
{
	Test st;
	st.set_first(10);
	st.set_second(20);
	st.set_third("hello");
	
	char buffer[64] = {0};
	if(!st.SerializeToArray(buffer,st.ByteSize()))
		cout<<"Serlialize Error"<<endl;
	return 0;
}
//由於序列化的結果中含有0,所以采用gdb調試打印看,結果如下:
$1 = "\b\n\020\024\032\005hello", '\000' <repeats 52 times>
下面的文獻內容,摘自大佬的博客,自己只是用來學習,附上地址,如有侵犯,聯系本人,會盡快處理。
https://blog.csdn.net/chengzi_comm/article/details/53199278

protobuf的message中每個字段的格式為: 修飾符 字段類型 字段名 = 域號;
在序列化時,protobuf按照TLV的格式序列化每一個字段,T即Tag,也叫Key;L是Value的長度,如果一個字段是整形,這個L部分會省略;V是該字段對應的值value。

序列化后的Value是按原樣保存到字符串或者文件中,Key按照一定的轉換條件保存起來,序列化后的結果就是 KeyValueKeyValue。

Key的序列化格式是按照message中字段后面的域號與字段類型來轉換。轉換公式如下:
(field_number << 3) | wire_type,wire_type與字段的類型有關(不同類型采用的壓縮方式不一)

 

Message Structure

PB 的 Message 是以一系列有序的 KV 構成的,key 就是字段的 number+wireType,這個在 IDL 文件定義的時候就有的;value 就是對應的值。這里的 Key 結構大致是這樣的:

(field_number << 3) | wire_type

field_number 就是字段序號,本身也是Varints 類型的,wire_type 表示字段類型,占用3個 bit。關於 wire type,這里有個對應關系表:

 

比如:int32,int64等采用varint(wire_type=0),string就采用Length-delimi(wire_type=2)
這里只列舉這兩個,其他的type類型可以去官網查看。

protobuf規定:
1.如果域號在[1,15]范圍內,會使用一個字節表示Key;
2.如果域號大於等於16,會使用兩個字節表示Key;
3.Key編碼過后,該字節的第一個比特位表示之后的一個字節是否與當前這個字節有關:
	a.如果第一個比特位為1,表示有關,即連續兩個字節都是Key的編碼;
	b.如果第一個比特位為0,表示Key的編碼只有當前一個字節,后面的字節是Length或者Value;
計算first
key = (1 << 3) | 0 = 8, 8對應的ASCLL就是‘\b’,因為是int型所以不需要指定長度
那么‘\n’就是value,10嘍。

second
key = (2 << 3) | 0 = 16, 16的八進制碼是20
24就是value(20)的八進制碼了。

third
key = (3 << 3) | 2 = 26,26的八進制碼是32,后面的5就是長度,以及后面的內容了

到這里我們只是把數據對應上了,體積小表現在哪?

我們可以清晰的看到10和20兩個4字節的數被編碼成了一個字節的八進制數。怎么做的。
例: 10的二進制碼 = 00000000 00000000 00000000 00001010
	1.從最后字節取7位,如果當前字節后面沒字節了,那么就在最高位補1,否則補0。
	2.講得到的字節拼接起來得到00000000 00000000 00000000 10001010
	3.如果字節表示的數為0,那么舍棄該字節,此時得到的就是10001010
做反序列化工作時:第一位是1,那么剩下7位都是數據,則為0001010 = 10。

如果數據比較大時,那么編碼可能會占用兩個字節,比如300:10101100 00000010 
解析工作:高位為0,代表這是最后一個字節,1代表還有數據。去掉最高位拼接得到 0000010 0101100

我的求證:

message UserLoginREQ { optional int64 uid = 1; optional string key = 2; optional string ver = 3; optional string ip = 4; optional int32 login_type = 6[default = 0]; //0.登錄 1.斷線重連 }

 

轉成的 PB序列化,

pb長度:0x58;

有缺省的pb字段。

szPbData 58 08 2F 12 20 62 64 64 30 37 32 37 30 62 64 63 63 64 64 66 61 33 61 66 38 34 66 31 30 65 37 38 65 39 63 66 34 1A 07 34 2E 31 39 2E 32 39 22 09 31 32 37 2E 30 2E 30 2E 31 30 00

 

 

 

發送的內容:

uid: 47 key: "34f3629dc31aa03782addddef6cbc86d" ver: "4.19.29" ip: "127.0.0.1" login_type: 0

 

 

完整的PB協議:

message UserLoginREQ
{
optional int64 uid = 1;
optional string key = 2;
optional string ver = 3;
optional string ip = 4;
optional int32 client_type = 5 [default = 1];//客戶端類型 1 cocos 2 h5
optional int32 login_type = 6[default = 0]; //0.登錄 1.斷線重連
optional string system = 7; //用戶系統信息ios or andirod or windows
optional bool is_multipc = 8[default=false]; // 多進程pc標志
}




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM