一.問題的提出
這里的編碼,具體是指如何高效地將基本類型序列化,以便在網絡上傳輸,不是指將整個數據包用utf8或base64等編碼,更不是應用層和業務相關的編碼。
假若有定義如下:
#pragma
pack(1)
typedef
struct tagS_HDR
{
uint8 nProtocol;//協議類型
uint16
nLength;//消息長度,不包括本消息頭
uint16
nType;//消息類型
uint8 nExt;//協議擴展標記,比如消息子類型
}S_HDR;
#pragma
pack()
typedef
struct tagS_FileTxHDR_Req
{
S_HDR hdr_;//消息頭
uint32 nUID_;//接收方用戶唯一ID
string sFileName_;//要傳送的文件名
uint32 nFileSize_;//文件大小
uint8 nFileID_;//臨時分配的文件ID,最多支持同時傳256個文件
}S_FileTxHDR_Req;
如果你是做服務器開發,對類似這樣的結構體應該非常熟悉,為了簡化問題,在繼續討論之前,我們做幾個假設:
(1)消息頭的長度是固定的,且設計足夠高效,不在編碼優化之列。
(2)整形里面只關注uint8,uint16.uint32.
(3)字符串關注兩類:以\0結尾的string和二進制流的bytes。
(4)其它類型暫不關注,不影響問題的討論,后續會慢慢完善。
(5)libprotobuf已經很強大,但也有限制,比如必須鏈接一個庫,如果是手機客戶端和服務器通訊,顯然是不能用的。
場景如下:A(ID為123)要給B(ID為456)發送file.txt文件,文件內容為”Hello World!”,傳統的序列化方式為nUID占用4個字節,nFileSize也占用4個字節,而事實上456用2個字節表示足以,” Hello World!”的長度用一個字節表示足以。明顯是浪費嘛!有沒有什么辦法解決這個問題呢?當然有,答案是:站在巨人的肩上。
二。Base 128 Varints
Google Protobuf里面提出了“Base 128 Varints”編碼,這是一種變字節長度的編碼,官方描述為:varints是用一個或多個字節序列化整形的一種方法。我理解要點有三個(1)操作是序列化(2)操作對象是整形(3)變長編碼。重點是最后一點,他是如何編碼的呢?
(1)除了最后一個字節,varint中的每個字節的最高位設為1,表示后面還有字節出現
(2)每個字節的低7位看成是一個組(group),這個組和他相鄰的下一個7位組共同存儲某個整形的“組合表示”,最低有效組在前面。
上面的定義可能比較生硬,我沒看具體例子之前,也沒搞明白,我們來看http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/encoding.html#varints中的例子:
(1)一個字節。下面只有一個字節,所以最高位是0,表示十進制1
0000 0001
(2)兩個字節。
1010 1100 0000 0010
由于第一個字節后面還有一個字節,所以第一個字節的最高位設置為1,表示后面還有后繼字節,第二個字節的最高位為0。去掉每個字節的最高位,我們對兩個字節進行分組。第一個7位組:0101100,第二個7位組:0000010,組合起來就是:0101100 0000010,由于最低有效組在前面,所以兩個7位組進行調換,結果為:0000010 0101100,中間的空格是為了大家區分,去掉前面的0,也就是:100101100,十進制為1*2^8 + 1*2^5 + 1*2^3 + 1*2^2 =
256 + 32 + 8 + 4 = 300。
(3)三個字節。我們換種方式,給定某個整形,要求寫出他的varint表示,這里當然是落在需要3個字節表示的整形。16380可以嗎?不可以!因為16380 < 2^14,所以2個字節足以,我們就以27491為例,27491的二進制表示為:
0110 1011 0110 0011
注意這里是高字節之前,低字節在后,和varint的編碼規則相反,首先按照7位一組劃分為:0000001 1010110 1100011,然后反轉為:1100011 1010110 0000001,最后將末尾的7位組前面補0組成一個字節,兩個7位組都補1分別組成一個字節:
11100011 11010110 00000001
(4)更多字節。聰明的你可能發現,varint編碼中4個字節最大表示的數為2^28,非常正確,同時說明了天下沒有免費的午餐,有得有失。Protobuf引入了fixed32解決這個問題的,如果某個字段經常大于2^28,應當優選fixed32,他是固定長度為32位的。
三.String和bytes
string和bytes的表示形式一致,都是“長度+值”,其中長度采用varint編碼,值為字符序列或者二進制碼流
如有錯誤,請指正,下次會寫到結構體類型的編碼,對應于libprotobuf的Message概念。