Thrift的TBinaryProtocol二进制协议分析

先上张图,说明一下thrift的二进制协议是什么东东。

Thrift的TBinaryProtocol二进制协议分析

报文格式编码:

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的二进制协议是什么样子?

Thrift的TBinaryProtocol二进制协议分析

接下来我们对照上面的图分析一下这个协议包,看能不能把每个字节的意义说明白。

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

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.

上一篇:利用Java编写简单的WebService实例


下一篇:笔试算法题(57):基于堆的优先级队列实现和性能分析(Priority Queue based on Heap)