Thrift的TBinaryProtocol二進制協議分析


先上張圖,說明一下thrift的二進制協議是什么東東。

報文格式編碼:

bool類型:

  一個字節的類型,兩個字節的字段編號,一個字節的值(true:1,false:0).

Byte型:

  一個字節的類型,兩個字節的字段編號,一個字節的值.

I16型:

  一個字節的類型,兩個字節的字段編號,兩個字節的值.

I32型:

  一個字節的類型,兩個字節的字段編號,四個字節的值.

I64型和double型:

  一個字節的類型,兩個字節的字段編號,八個字節的值.

String型:

  一個字節的類型,兩個字節的字段編號,四個字節的負載數據長度,負載數據的值.

Struct型:

  一個字節的類型,兩個字節的字段編號,結構體負載數據,一個字節的結束標記.

MAP型:

  一個字節的類型,兩個字節的字段編號,一個字節的鍵類型,一個字節的值類型,四個字節的負載數據長度,負載數據的值.

Set型:

  一個字節的類型,兩個字節的字段編號,一個字節的值類型,四個字節的負載數據長度,負載數據的值.

List型:

  一個字節的類型,兩個字節的字段編號,一個字節的值類型,四個字節的負載數據長度,負載數據的值.

消息(函數)型:

  表示方式一:四個字節的版本(含調用類型),四個字節的消息名稱長度,消息名稱,四個字節的流水號,消息負載數據的值,一個字節的結束標記。

  表示方式二:四個字節的消息名稱長度,消息名稱,一個字節調用類型,四個字節的流水號,消息負載數據的值,一個字節的結束標記。

  對嚴格的thrift消息,必須包含32為版本信息。

  若有32為版本信息,函數調用(請求:1,響應:2,異常:3,無返回值的請求:4)被包含到32為版本中,不獨立出現。

  計算方法:

  32位版本 = 0x8001000 & 函數調用

  計算后,請求報文的32為版本值為 0x80010001;響應報文的32為版本值為 0x80010002;異常報文的32為版本值為 0x80010003;

  若沒有32為版本信息時,函數調用(請求:1,響應:2,異常:3,無返回值的請求:4)獨立出現在消息報文中。

thrift的IDL文件如下:

struct ArgStruct {
    1:byte argByte,
    2:string argString
    3:i16  argI16,
    4:i32  argI32,
    5:i64  argI64,
    6:double argDouble,
    
}

service RpcService {
    list<string> funCall(
        1:ArgStruct argStruct,
        2:byte argByte,
        3:i16  argI16,
        4:i32  argI32,
        5:i64  argI64,
        6:double argDouble,
        7:string argString,
        8:map<string, string> paramMapStrStr,
        9:map<i32, string> paramMapI32Str,
        10:set<string> paramSetStr,
        11:set<i64> paramSetI64,
        12:list<string> paramListStr,
        ),
}

生成lua代碼

thrift -gen lua rpcbin.thrift

 

寫一個小的測試例子客戶端(lua):

 

require "rpcbin_RpcService"
require "TFramedTransport"
require "TBinaryProtocol"
require "TSocket"

function demoFunc()
    local socket = TSocket:new{
        host='127.0.0.1',
        port=8090
    }
    local protocol = TBinaryProtocol:new{
        trans = socket
    }
    client = RpcServiceClient:new{
        protocol = protocol
    }
    local argStruct = ArgStruct:new{
      argByte = 53,
      argString = "str value",
      argI16 = 54,
      argI32 = 654321,
      argI64 = 334455,
      argDouble = 4334.55
    }
    -- Open the socket  
    socket:open()
    pmap = {}
    pmap.name = "namess"
    pmap.pass = "vpass"
    pistrmap = {}
    pistrmap[2] = "str2"
    pistrmap[3] = "str3"
    ret = client:funCall(argStruct, 65, 2533, 4455,
        98765, 3.2212, "login", pmap,
        pistrmap,
        {"ele1", "ele2", "ele3"},
        {1,2,3,4},
        {"l1","l2","l3"});
    for k,v in pairs(ret)
    do
        print(k, v)
    end
    socket:close()
end
demoFunc()

 

一些依賴文件代碼

Thrift.lua:

TType = {
  STOP   = 0,
  VOID   = 1,
  BOOL   = 2,
  BYTE   = 3,
  I08    = 3,
  DOUBLE = 4,
  I16    = 6,
  I32    = 8,
  I64    = 10,
  STRING = 11,
  UTF7   = 11,
  STRUCT = 12,
  MAP    = 13,
  SET    = 14,
  LIST   = 15,
  UTF8   = 16,
  UTF16  = 17
}

TMessageType = {
  CALL  = 1,
  REPLY = 2,
  EXCEPTION = 3,
  ONEWAY = 4
}
rpcbin_RpcService.lua line:298-321

  if self.paramSetStr then
    oprot:writeFieldBegin('paramSetStr', TType.SET, 10)
    oprot:writeSetBegin(TType.STRING, ttable_size(self.paramSetStr))
    for _,iter31 in pairs(self.paramSetStr) do
      oprot:writeString(iter31)
    end
    oprot:writeSetEnd()
    oprot:writeFieldEnd()
  end
  if self.paramSetI64 then
    oprot:writeFieldBegin('paramSetI64', TType.SET, 11)
    oprot:writeSetBegin(TType.I64, ttable_size(self.paramSetI64))
    for _,iter32 in pairs(self.paramSetI64) do
      oprot:writeI64(iter32)
    end
    oprot:writeSetEnd()
    oprot:writeFieldEnd()
  end

需要修改一下set和list的取值方式,thrift生成的代碼有個問題(對set和list只寫下標不傳值),所以做如下修改:

for iter31,_ in pairs(self.paramSetStr) do 改成 for _,iter31 in pairs(self.paramSetStr) do
for iter32,_ in pairs(self.paramSetI64) do 改成 for _,iter32 in pairs(self.paramSetI64) do
for iter33,_ in ipairs(self.paramListStr) do 改成  for _,iter33 in ipairs(self.paramListStr) do

  

執行lua cln.lua前抓包看看thrift的二進制協議是什么樣子?

 

接下來我們對照上面的圖分析一下這個協議包,看能不能把每個字節的意義說明白。 

0000 00 00 00 07
0000 66 75 6e 43 61 6c 6c 01 00 00 00 01 0c 00 01 03
0010 00 01 35 0b 00 02 00 00 00 09 73 74 72 20 76 61
0020 6c 75 65 06 00 03 00 36 08 00 04 00 09 fb f1 0a
0030 00 05 00 00 00 00 00 05 1a 77 04 00 06 cd cc cc
0040 cc 8c ee b0 40 00 03 00 02 41 06 00 03 09 e5 08
0050 00 04 00 00 11 67 0a 00 05 00 00 00 00 00 01 81
0060 cd 04 00 06 69 00 6f 81 04 c5 09 40 0b 00 07 00
0070 00 00 05 6c 6f 67 69 6e 0d 00 08 0b 0b 00 00 00
0080 02 00 00 00 04 6e 61 6d 65 00 00 00 06 6e 61 6d
0090 65 73 73 00 00 00 04 70 61 73 73 00 00 00 05 76
00a0 70 61 73 73 0d 00 09 08 0b 00 00 00 02 00 00 00
00b0 02 00 00 00 04 73 74 72 32 00 00 00 03 00 00 00
00c0 04 73 74 72 33 0e 00 0a 0b 00 00 00 03 00 00 00
00d0 04 65 6c 65 31 00 00 00 04 65 6c 65 32 00 00 00
00e0 04 65 6c 65 33 0e 00 0b 0a 00 00 00 04 00 00 00
00f0 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00
0100 00 00 00 00 03 00 00 00 00 00 00 00 04 0f 00 0c
0110 0b 00 00 00 03 00 00 00 02 6c 31 00 00 00 02 6c
0120 32 00 00 00 02 6c 33 00


4 個字節00 00 00 07 表示長度7;
7 個字節66 75 6e 43 61 6c 6c 表示長度7的值funCall;
1 個字節01 表示消息請求 TMessageType.CALL = 1;
4 個字節00 00 00 01 表示請求流水號1;

函數funCall的第一個參數:
3 個字節0c 00 01表示結構體TType.STRUCT = 12,函數funCall的第一個參數的編號是1, 1:ArgStruct argStruct,

struct ArgStruct {
1:byte argByte,
2:string argString
3:i16 argI16,
4:i32 argI32,
5:i64 argI64,
6:double argDouble,

}
結構體分析如下:
結構體第一個元素:
4 個字節03 00 01 35 表示ArgStruct的第一個元素1:byte argByte,
類型為03,TType.BYTE = 3,元素編號為1,值為16進制35,即10進制53;

結構體第二個元素:
16個字節0b 00 02 00 00 00 09 73 74 72 20 76 61 6c 75 65
表示ArgStruct的第二個元素2:string argString,
類型為0b,TType.STRING = 11,元素編號00 02為2,
值的長度00 00 00 09為9,值73 74 72 20 76 61 6c 75 65為str value;

結構體第三個元素:
5個字節06 00 03 00 36 表示ArgStruct的第三個元素3:i16 argI16,
類型為06,TType.I16 = 6,元素編號00 03為3,
值00 36為54;

結構體第四個元素:
7個字節08 00 04 00 09 fb f1 表示ArgStruct的第四個元素4:i32 argI32,
類型為08,TType.I32 = 8,元素編號00 04為4,
值00 09 fb f1為654321;

結構體第五個元素:
11個字節0a 00 05 00 00 00 00 00 05 1a 77 表示ArgStruct的第五個元素5:i64 argI64,
類型為0a,TType.I64 = 10,元素編號00 05為5,
值00 00 00 00 00 05 1a 77為334455;

結構體第六個元素:
12個字節04 00 06 cd cc cc cc 8c ee b0 40 00 表示ArgStruct的第六個元素6:double argDouble,
類型為04,TType.DOUBLE = 4,元素編號00 06為6,
值cd cc cc cc 8c ee b0 40為4334.55,最后一個00表示結構體結束TType.STOP = 0;

函數funCall的第二個參數:
2:byte argByte,
4個字節03 00 02 41 表示類型為03,TType.BYTE = 3,參數編號00 02為2,
值41為65;


函數funCall的第三個參數:
3:i16 argI16,
5個字節06 00 03 09 e5 表示類型為TType.I16 = 6,元素編號00 03為3,
值09 e5為2533;


函數funCall的第四個參數:
4:i32 argI32,
字節08 00 04 00 00 11 67 表示類型為08,TType.I32 = 8,元素編號00 04為4,
值00 00 11 67為4455;


函數funCall的第五個參數:
5:i64 argI64,
字節0a 00 05 00 00 00 00 00 01 81 cd 表示類型為0a,TType.I64 = 10,元素編號00 05為5,
值00 00 00 00 00 01 81 cd為98765;


函數funCall的第六個參數:
6:double argDouble,
4個字節04 00 06 69 00 6f 81 04 c5 09 40 表示類型為04,TType.DOUBLE = 4,元素編號00 06為6,
值69 00 6f 81 04 c5 09 40為3.2212;


函數funCall的第七個參數:
7:string argString,
4個字節0b 00 07 00 00 00 05 6c 6f 67 69 6e 表示類型為0b,TType.STRING = 11,元素編號00 07為7,值的長度00 00 00 05為5,
值6c 6f 67 69 6e為login;


函數funCall的第八個參數:
8:map<string, string> paramMapStrStr,
字節0d 00 08 0b 0b 00 00 00 02 00 00 00 04 6e 61 6d 65 00 00 00 06 6e 61 6d 65 73 73 00 00 00 04 70 61 73 73 00 00 00 05 76 70 61 73 73

表示類型為0d,TType.MAP = 13,元素編號00 08為8,map的鍵和值的類型0b 0b為,TType.STRING = 11,
Map里面有2個元素00 00 00 02為2,
第一個元素的鍵長度00 00 00 04為4,鍵的值6e 61 6d 65為name,
值長度00 00 00 06為6,值的值6e 61 6d 65 73 73為namess,
第二個元素的鍵長度00 00 00 04為4,鍵的值0 61 73 73為pass,
值長度00 00 00 05為5,值的值76 70 61 73 73為vpass;


函數funCall的第九個參數:
9:map<i32, string> paramMapI32Str,
字節0d 00 09 08 0b 00 00 00 02 00 00 00 02 00 00 00 04 73 74 72 32 00 00 00 03 00 00 00 04 73 74 72 33

表示類型為0d,TType.MAP = 13,元素編號00 09為9,paramMapI32Str,map的鍵的類型08,TType.I32 = 8;map的值的類型0b為,TType.STRING = 11,
Map里面有2個元素00 00 00 02為2,
第一個元素鍵的值00 00 00 02為2,
值長度00 00 00 04為4,值的值73 74 72 32為str2,
第二個元素鍵的值00 00 00 03為3,
值長度00 00 00 04為4,值的值73 74 72 33為str3;


函數funCall的第十個參數:
10:set<string> paramSetStr,
字節0e 00 0a 0b 00 00 00 03 00 00 00 04 65 6c 65 31 00 00 00 04 65 6c 65 32 00 00 00 04 65 6c 65 33

表示類型為0e,TType.SET = 14,元素編號00 0a為10,
set的值的類型0b為,TType.STRING = 11,
set里面有3個元素00 00 00 03為3,
第一個元素值長度00 00 00 04為4,值65 6c 65 31為ele1,
第二個元素值長度00 00 00 04為4,值65 6c 65 32為ele2,
第三個元素值長度00 00 00 04為4,值65 6c 65 33為ele3;


函數funCall的第十一個參數:
11:set<i64> paramSetI64,
字節0e 00 0b 0a 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 04

表示類型為0e,TType.SET = 14,元素編號00 0b為11,
set的值的類型0a為,TType.I64 = 10,
set里面有4個元素00 00 00 04為4,
第一個元素值00 00 00 00 00 00 00 01為1,
第二個元素值00 00 00 00 00 00 00 02為2,
第三個元素值00 00 00 00 00 00 00 03為3,
第四個元素值00 00 00 00 00 00 00 04為4,


函數funCall的第十二個參數:
12:list<string> paramListStr,
字節0f 00 0c 0b 00 00 00 03 00 00 00 02 6c 31 00 00 00 02 6c 32 00 00 00 02 6c 33 00

表示類型為0f,TType.LIST = 15,元素編號00 0c為12,
list的值的類型0b為,TType.STRING = 11,
set里面有3個元素00 00 00 03為3,
第一個元素值長度00 00 00 02為2,值6c 31為l1,
第二個元素值長度00 00 00 02為2,值6c 32為l2,
第三個元素值長度00 00 00 02為2,值6c 33為l3;
最后一個00表示結構體結束TType.STOP = 0;

 

demo git:https://github.com/gityf/lua

Done.

 


免責聲明!

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



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