thrift的TCompactProtocol紧凑型二进制协议分析

Thrift的紧凑型传输协议分析:

用一张图说明一下Thrift的TCompactProtocol中各个数据类型是怎么表示的。

报文格式编码:

bool类型:

  一个字节。

  如果bool型的字段是结构体或消息的成员字段并且有编号,一个字节的高4位表示字段编号,低4位表示bool的值(0001:true, 0010:false),即:一个字节的低4位的值(true:1,false:2).

  如果bool型的字段单独存在,一个字节表示值,即:一个字节的值(true:1,false:2).

Byte型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一个字节的值.

I16型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一至三个字节的值.

I32型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一至五个字节的值.

I64型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一至十个字节的值.

double型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),八个字节的值.

  注:把double类型的数据转成八字节保存,并用小端方式发送。

String型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一至五个字节的负载数据的长度,负载数据.

Struct型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),结构体负载数据,一个字节的结束标记.

MAP型:

  一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一至五个字节的map元素的个数,一个字节的键值类型组合(高4位键类型,低4位值类型),Map负载数据.

Set型:

  表示方式一:一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一个字节的元素个数和值类型组合(高4位键元素个数,低4位值类型),Set负载数据.

  适用于Set中元素个数小于等于14个的情况。

  表示方式二:一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一个字节的键值类型(高4位全为1,低4位值类型),一至五个字节的map元素的个数,Set负载数据.

  适用于Set中元素个数大于14个的情况。

List型:

  表示方式一:一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一个字节的元素个数和值类型组合(高4位键元素个数,低4位值类型),List负载数据.

  适用于Set中元素个数小于等于14个的情况。

  表示方式二:一个字节的编号与类型组合(高4位编号偏移1,低4位类型),一个字节的键值类型(高4位全为1,低4位值类型),一至五个字节的map元素的个数,List负载数据.

  适用于Set中元素个数大于14个的情况。

消息(函数)型:

  一个字节的版本,一个字节的消息调用(请求:0x21,响应:0x41,异常:0x61,oneway:0x81),一至五个字节的消息名称长度,消息名称,消息参数负载数据,一个字节的结束标记。

以上说明是基于相邻字段的编号小于等于15的情况。

如果字段相邻编号大于15,需要把类型和编号分开表示:用一个字节表示类型,一至五个字节表示编号偏移值。

阅读到这里,或许会疑问,为什么数值型的值用 “一至五个字节”表示?

原因:对数值进行压缩,压缩算法就是Varint,如下简单的说明一下什么是Varint数值压缩。

Varint数值压缩

一个整数一般是以32位来表示的,存储需要4个字节。

当如果整数大小在256以内,那么只需要用一个字节就可以存储这个整数,这样剩下的3个字节的存储空间空闲。

当如果整数大小在256到65536之间,那么只需要用两个字节就可以存储这个整数,这样剩下的2个字节的存储空间空闲。

当如果整数大小在65536到16777216之间,那么只需要用三个字节就可以存储这个整数,这样剩下的1个字节的存储空间空闲。

当如果整数大小在16777216到4294967296之间,那么需要用四个字节存储这个整数。

这时,Google引入了varint,把表示整数的空闲空间压缩,用这种思想来序列化整数。

这种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。

Varint将数按照7位分段,把一个整数压缩后存储。

Varint 中的每个字节的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。

其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,会用两个字节。

这样就可以实现数值压缩。

采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。

从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。

实现Varint32代码:

uint32_t TCompactProtocolT<Transport_>::writeVarint32(uint32_t n) {
  uint8_t buf[5];
  uint32_t wsize = 0;

  while (true) {
    if ((n & ~0x7F) == 0) {
      buf[wsize++] = (int8_t)n;
      break;
    } else {
      buf[wsize++] = (int8_t)((n & 0x7F) | 0x80);
      n >>= 7;
    }
  }
  trans_->write(buf, wsize);
  return wsize;
}

同样的方式实现Varint64代码:

uint32_t TCompactProtocolT<Transport_>::writeVarint64(uint64_t n) {
  uint8_t buf[10];
  uint32_t wsize = 0;

  while (true) {
    if ((n & ~0x7FL) == 0) {
      buf[wsize++] = (int8_t)n;
      break;
    } else {
      buf[wsize++] = (int8_t)((n & 0x7F) | 0x80);
      n >>= 7;
    }
  }
  trans_->write(buf, wsize);
  return wsize;
}

或许你会疑问,如果一个整数最高位和比较低位为1,也就是说负数用varint怎么压缩?

既然正数可以用varint很好的压缩,能不能把负数转变成正数后再用varint做数值压缩呢?

答案是:Yes.

怎么把负数转成正数:

引入一个叫Zigzag的算法,那Zigzag到底是什么呢?

Zigzag算法

正数:当前的数乘以2, zigzagY = x * 2

负数:当前的数乘以-2后减1, zigzagY = x * -2 - 1

用程序的移位表示就是:

(n << 1) ^ (n >> 31) //int32
(n << 1> ^ (n >> 63) //int64 

代码表示:

/**
 * Convert l into a zigzag long. This allows negative numbers to be
 * represented compactly as a varint.
 */
template <class Transport_>
uint64_t TCompactProtocolT<Transport_>::i64ToZigzag(const int64_t l) {
  return (l << 1) ^ (l >> 63);
}

/**
 * Convert n into a zigzag int. This allows negative numbers to be
 * represented compactly as a varint.
 */
template <class Transport_>
uint32_t TCompactProtocolT<Transport_>::i32ToZigzag(const int32_t n) {
  return (n << 1) ^ (n >> 31);
}

Thrift中对数值的发送做法是:先做zigzag得到一个数,再做varint数值压缩。

下面用一个例子说明一下Thrift的TCompactProtocol协议。

建一个rpc.thrift的IDL文件。

namespace go demo.rpc
namespace cpp demo.rpc
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,
        ),
}

使用命令生成go代码

thrift --gen go -o src rpc.thrift

编写一个go的thrift客户端:

package main

import (
    "demo/rpc"
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "net"
    "os"
    "time"
)

func main() {
    startTime := currentTimeMillis()
    //transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    transportFactory := thrift.NewTTransportFactory()
    //protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    //protocolFactory := thrift.NewTJSONProtocolFactory()
    //protocolFactory := thrift.NewTSimpleJSONProtocolFactory()
    protocolFactory := thrift.NewTCompactProtocolFactory()

    transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "8090"))
    if err != nil {
        fmt.Fprintln(os.Stderr, "error resolving address:", err)
        os.Exit(1)
    }

    useTransport := transportFactory.GetTransport(transport)
    client := rpc.NewRpcServiceClientFactory(useTransport, protocolFactory)
    if err := transport.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:8090", " ", err)
        os.Exit(1)
    }
    defer transport.Close()

    argStruct := &rpc.ArgStruct{}
    argStruct.ArgByte = 53
    argStruct.ArgString = "str value"
    argStruct.ArgI16 = 54
    argStruct.ArgI32 = 12
    argStruct.ArgI64 = 43
    argStruct.ArgDouble = 11.22
    paramMap := make(map[string]string)
    paramMap["name"] = "namess"
    paramMap["pass"] = "vpass"
    paramMapI32Str := make(map[int32]string)
    paramMapI32Str[10] = "val10"
    paramMapI32Str[20] = "val20"
    paramSetStr := make(map[string]bool)
    paramSetStr["ele1"] = true
    paramSetStr["ele2"] = true
    paramSetStr["ele3"] = true
    paramSetI64 := make(map[int64]bool)
    paramSetI64[11] = true
    paramSetI64[22] = true
    paramSetI64[33] = true
    paramListStr := []string{"l1.","l2."}
    r1, e1 := client.FunCall(argStruct,
        53, 54, 12, 34, 11.22, "login",
        paramMap,paramMapI32Str,
        paramSetStr, paramSetI64, paramListStr)
    fmt.Println("Call->", r1, e1)

    endTime := currentTimeMillis()
    fmt.Println("Program exit. time->", endTime, startTime, (endTime - startTime))
}

func currentTimeMillis() int64 {
    return time.Now().UnixNano() / 1000000
}

编写简单测试的go服务端:

package main

import (
    "demo/rpc"
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "os"
)

const (
    NetworkAddr = ":8090"
)

type RpcServiceImpl struct {
}

func (this *RpcServiceImpl) FunCall(argStruct *rpc.ArgStruct,
    argByte int8, argI16 int16, argI32 int32,
    argI64 int64, argDouble float64, argString string,
    paramMapStrStr map[string]string, paramMapI32Str map[int32]string,
    paramSetStr map[string]bool, paramSetI64 map[int64]bool,
    paramListStr []string) (r []string, err error) {
    fmt.Println("-->FunCall:", argStruct)
    r = append(r, "return 1 by FunCall.")
    r = append(r, "return 2 by FunCall.")
    return
}

func main() {
    //transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    transportFactory := thrift.NewTTransportFactory()
    //protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    protocolFactory := thrift.NewTCompactProtocolFactory()
    //protocolFactory := thrift.NewTJSONProtocolFactory()
    //protocolFactory := thrift.NewTSimpleJSONProtocolFactory()

    serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
    if err != nil {
        fmt.Println("Error!", err)
        os.Exit(1)
    }

    handler := &RpcServiceImpl{}
    processor := rpc.NewRpcServiceProcessor(handler)

    server := thrift.NewTSimpleServer4(processor, serverTransport,transportFactory, protocolFactory)
    fmt.Println("thrift server in", NetworkAddr)
    server.Serve()
}

go build rpcclient.go生成可执行文件rpcclient后执行。

执行前抓包进行分析。

请求:
0000   82 21 01 07 66 75 6e 43 61 6c 6c 1c 13 35 18 09
0010   73 74 72 20 76 61 6c 75 65 14 6c 15 18 16 56 17
0020   71 3d 0a d7 a3 70 26 40 00 13 35 14 6c 15 18 16
0030   44 17 71 3d 0a d7 a3 70 26 40 18 05 6c 6f 67 69
0040   6e 1b 02 88 04 6e 61 6d 65 06 6e 61 6d 65 73 73
0050   04 70 61 73 73 05 76 70 61 73 73 1b 02 58 14 05
0060   76 61 6c 31 30 28 05 76 61 6c 32 30 1a 38 04 65
0070   6c 65 31 04 65 6c 65 32 04 65 6c 65 33 1a 36 16
0080   2c 42 19 28 03 6c 31 2e 03 6c 32 2e 00

响应:
0000   82 41 01 07 66 75 6e 43 61 6c 6c 09 00 28 14 72
0010   65 74 75 72 6e 20 31 20 62 79 20 46 75 6e 43 61
0020   6c 6c 2e 14 72 65 74 75 72 6e 20 32 20 62 79 20
0030   46 75 6e 43 61 6c 6c 2e 00

开始分析抓包的请求数据。

消息头分析:

第一个字节 82 表示:COMPACT协议版本。

COMPACT_PROTOCOL_ID       = 0x082

第二个字节21表示:消息请求,如何计算得到21呢? 

	COMPACT_VERSION           = 1
	COMPACT_VERSION_MASK      = 0x1f
	COMPACT_TYPE_MASK         = 0x0E0
	COMPACT_TYPE_BITS         = 0x07
	COMPACT_TYPE_SHIFT_AMOUNT = 5
	(COMPACT_VERSION & COMPACT_VERSION_MASK) | ((byte(typeId) << COMPACT_TYPE_SHIFT_AMOUNT) & COMPACT_TYPE_MASK)

  消息请求的message TypeId为1,带入计算  

(0x01 & 0x1f) | ((0x01 << 5) & 0xe0

   = 0x01 | 0x20 & 0xe0

  = 0x01 | 0x20

  = 0x21

第三个字节 01 为varint后的流水号 01.

第四个字节 07 为varint后消息的长度 07.

字节 66 75 6e 43 61 6c 6c 为消息名称字符串 funCall

开始解析参数:

函数funCall的第一个参数:

1:ArgStruct argStruct,

字节1c 表示结构体,高4为1表示编号偏移1,低4为c表示类型 0x0c为结构体。

偏移自加1保存,用于下一个字段编号偏移计算。

    argStruct.ArgByte = 53
    argStruct.ArgString = "str value"
    argStruct.ArgI16 = 54
    argStruct.ArgI32 = 12
    argStruct.ArgI64 = 43    argStruct.ArgDouble = 11.22

  结构体的第一个成员;

  字节 13 35 表示结构体第一个成员ArgByte,

  高4为1表示编号偏移1,低4为3表示类型 0x03为字节类型,值35就是十进制赋值的53.

  结构体的第二个成员;

  字节 18 09 73 74 72 20 76 61 6c 75 65表示结构体第二个成员ArgString,

  高4为1表示编号偏移1,低4位8表示类型 0x08为二进制字符串类型,

  09 表示varint后字符串的长度 9,值73 74 72 20 76 61 6c 75 65为字符串"str value"

  结构体的第三个成员;

  字节 14 6c 表示结构体第一个成员ArgI16,

  高4为1表示编号偏移1,低4为4表示类型 0x04为16位数值类型,值6c,二进制 110 1100,右移动一位,做zigzag解压后,得到 11 0110, 就是十进制赋值的54.

  结构体的第四个成员;

  字节 15 18 表示结构体第一个成员ArgI32,

  高4为1表示编号偏移1,低4为5表示类型 0x05为32位数值类型,值18,二进制 1 1000,右移动一位,做zigzag解压后,得到 1100, 就是十进制赋值的12.

  结构体的第五个成员;

  字节 16 56 表示结构体第一个成员ArgI64,

  高4为1表示编号偏移1,低4为6表示类型 0x06为64位数值类型,值56,二进制 101 0110,右移动一位,做zigzag解压后,得到 10 1011, 就是十进制赋值的43.

  结构体的第六个成员;

  字节 17 71 3d 0a d7 a3 70 26 40 表示结构体第一个成员ArgDouble,

  高4为1表示编号偏移1,低4为7表示类型 0x07为double数值类型,值71 3d 0a d7 a3 70 26 40,为11.22.

  结构体的结束标记

  字节 00 表示结构体结束。

函数funCall的第二个参数:

  2:byte argByte,  

字节 13 35 表示ArgByte,

高4为1表示编号偏移1,低4为3表示类型 0x03为字节类型,值35就是十进制赋值的53.

函数funCall的第三个参数:
  3:i16 argI16,

字节 14 6c 表示ArgI16,

 高4为1表示编号偏移1,低4为4表示类型 0x04为16位数值类型,值6c,二进制 110 1100,右移动一位,做zigzag解压后,得到 11 0110, 就是十进制赋值的54.

函数funCall的第四个参数:
  4:i32 argI32,

字节 15 18 表示ArgI32,

高4为1表示编号偏移1,低4为5表示类型 0x05为32位数值类型,值18,二进制 1 1000,右移动一位,做zigzag解压后,得到 1100, 就是十进制赋值的12.

函数funCall的第五个参数:
  5:i64 argI64,

字节 16 44 表示ArgI64,

高4为1表示编号偏移1,低4为6表示类型 0x06为64位数值类型,值44,二进制 100 0100,右移动一位,做zigzag解压后,得到 10 0010, 就是十进制赋值的34.

函数funCall的第六个参数:
  6:double argDouble,

字节 17 71 3d 0a d7 a3 70 26 40 表示ArgDouble,

 高4为1表示编号偏移1,低4为7表示类型 0x07为double数值类型,值71 3d 0a d7 a3 70 26 40,为11.22.

函数funCall的第七个参数:
  7:string argString,

字节 18 05 6c 6f 67 69 6e表示ArgString,

高4为1表示编号偏移1,低4位8表示类型 0x08为二进制字符串类型,

05 表示varint后字符串的长度 5,值 6c 6f 67 69 6e为字符串"login"

函数funCall的第八个参数:
  8:map<string, string> paramMapStrStr,

字节 1b 02 88 04 6e 61 6d 65 06 6e 61 6d 65 73 73 04 70 61 73 73 05 76 70 61 73 73表示paramMapStrStr

高4位1表示编号偏移1,低4位b表示类型 0x0b为Map类型,

02 表示varint后Map元素的个数 2,

88 表示Map元素的键和值的类型都为二进制字符串(高4位 8表示键的类型 0x08 为二进制字符串类型,低4位8表示值的类型 0x08 为二进制字符串类型)

Map的第一个键:  04 6e 61 6d 65 为长度为4的字符串 6e 61 6d 65 值 "name"

Map的第一个键的值:06 6e 61 6d 65 73 73 为长度为6的字符串 6e 61 6d 65 73 73值 "namess"

Map的第二个键:  04 70 61 73 73 为长度为4的字符串 70 61 73 73 值 "pass"

Map的第二个键的值:05 76 70 61 73 73 为长度为5的字符串 76 70 61 73 73值 "vpass"

函数funCall的第九个参数:
  9:map<i32, string> paramMapI32Str,

字节 1b 02 58 14 05 76 61 6c 31 30 28 05 76 61 6c 32 30表示paramMapI32Str,

高4位1表示编号偏移1,低4位b表示类型 0x0b为Map类型,

02 表示varint后Map元素的个数 2,

58 表示Map元素的键和值的类型都为二进制字符串(高4位 5表示键的类型 0x05 为32位数值类型,低4位8表示值的类型 0x08 为二进制字符串类型)

Map的第一个键:  14,二进制 1 0100,右移动一位,做zigzag解压后,得到 1010, 就是十进制赋值的10.

Map的第一个键的值:05 76 61 6c 31 30为长度为5的字符串 76 61 6c 31 30值 "val10"

Map的第二个键: 28,二进制 101 000,右移动一位,做zigzag解压后,得到 1 0100, 就是十进制赋值的20.

Map的第二个键的值:5 76 61 6c 32 30 为长度为5的字符串 76 70 61 73 73值 "val20"

函数funCall的第十个参数:
  10:set<string> paramSetStr,

字节 1a 38 04 65 6c 65 31 04 65 6c 65 32 04 65 6c 65 33表示paramSetStr,

高4位1表示编号偏移1,低4位a表示类型 0x0a为Set类型,

38 表示元素的个数和类型(高4位3表示set有3个元素,低4位8表示值的类型 0x08 为二进制字符串类型)

Set的第一个值: 04 65 6c 65 31,长度为4的字符串65 6c 65 31为"ele1"

Set的第二个值: 04 65 6c 65 32,长度为4的字符串65 6c 65 32为"ele2"

Set的第三个值: 04 65 6c 65 33,长度为4的字符串65 6c 65 33为"ele3"

函数funCall的第十一个参数:
  11:set<i64> paramSetI64,

字节 1a 36 16 2c 42表示paramSetI64,

高4位1表示编号偏移1,低4位a表示类型 0x0a为Set类型,

36 表示元素的个数和类型(高4位3表示set有3个元素,低4位6表示值的类型 0x06 为64为数值类型)

Set的第一个值: 16,二进制 10110,右移动一位,做zigzag解压后,得到 1011, 就是十进制赋值的11.

Set的第二个值: 2c,二进制 101100,右移动一位,做zigzag解压后,得到 10110, 就是十进制赋值的22.

Set的第三个值: 42,二进制 1000010,右移动一位,做zigzag解压后,得到 100001, 就是十进制赋值的33.

函数funCall的第十二个参数:
  12:list<string> paramListStr,

字节 19 28 03 6c 31 2e 03 6c 32 2e表示paramListStr,

高4位1表示编号偏移1,低4位9表示类型 0x09为List类型,

28 表示元素的个数和类型(高4位3表示set有2个元素,低4位8表示值的类型 0x08 为二进制字符串类型)

List的第一个值: 03 6c 31 2e,长度为3的字符串6c 31 2e为"l1."

List的第二个值: 03 6c 32 2e,长度为3的字符串6c 32 2e为"l2."

最后一个字节 00 表示消息结束。

------------------------------------------------------------------------------------------------------------

开始分析抓包的响应数据。

响应:
0000   82 41 01 07 66 75 6e 43 61 6c 6c 09 00 28 14 72
0010   65 74 75 72 6e 20 31 20 62 79 20 46 75 6e 43 61
0020   6c 6c 2e 14 72 65 74 75 72 6e 20 32 20 62 79 20
0030   46 75 6e 43 61 6c 6c 2e 00

  

第一个字节 82 表示:COMPACT协议版本。

COMPACT_PROTOCOL_ID       = 0x082

第二个字节41表示:消息请求,如何计算得到41呢? 

	COMPACT_VERSION           = 1
	COMPACT_VERSION_MASK      = 0x1f
	COMPACT_TYPE_MASK         = 0x0E0
	COMPACT_TYPE_BITS         = 0x07
	COMPACT_TYPE_SHIFT_AMOUNT = 5
	(COMPACT_VERSION & COMPACT_VERSION_MASK) | ((byte(typeId) << COMPACT_TYPE_SHIFT_AMOUNT) & COMPACT_TYPE_MASK)

  消息请求的message TypeId为1,带入计算 

(0x01 & 0x1f) | ((0x02 << 5) & 0xe0
    = 0x01 | 0x40 & 0xe0
  = 0x01 | 0x40
  = 0x41

第三个字节 01 为varint后的流水号 01.

第四个字节 07 为varint后消息的长度 07.

字节 66 75 6e 43 61 6c 6c 为消息名称字符串 funCall

响应参数:

list<string>

字节 09 00 28 14 72 65 74 75 72 6e 20 31 20 62 79 20 46 75 6e 43 61 6c 6c 2e 14 72 65 74 75 72 6e 20 32 20 62 79 20 46 75 6e 43 61 6c 6c 2e

09 表示类型 0x09为List类型,

00 表示响应时字段的编号为0(返回值确实没有编号),由于返回值没有字段编号,所以类型和编号要分开到不同的字节里面。

28 表示元素的个数和类型(高4位3表示set有2个元素,低4位8表示值的类型 0x08 为二进制字符串类型)

List的第一个值: 14 72 65 74 75 72 6e 20 31 20 62 79 20 46 75 6e 43 61 6c 6c 2e,长度为20的字符串72 65 74 75 72 6e 20 31 20 62 79 20 46 75 6e 43 61 6c 6c 2e为"return 1 by FunCall."

List的第二个值: 14 72 65 74 75 72 6e 20 32 20 62 79 20 46 75 6e 43 61 6c 6c 2e,长度为20的字符串72 65 74 75 72 6e 20 32 20 62 79 20 46 75 6e 43 61 6c 6c 2e为"return 2 by FunCall."

最后一个字节00表示响应消息结束。

Done.

时间: 2024-08-04 14:04:28

thrift的TCompactProtocol紧凑型二进制协议分析的相关文章

Thrift的TJsonProtocol协议分析

Thrift协议实现目前有二进制协议(TBinaryProtocol),紧凑型二进制协议(TCompactProtocol)和Json协议(TJsonProtocol). 前面的两篇文字从编码和协议原理方面分析了TBinaryProtocol和TCompactProtocol协议,下面对TJsonProtocol协议做一下分析. TJsonProtocol协议相对比较简单,在网络中以文本方式传输,易于抓包分析和理解. 1. 数据类型表示方式和简写 数据类型 数据类型 Json协议节点简写 C++

NetAnalyzer笔记 之 二. 简单的协议分析

[创建时间:2015-08-27 22:15:17] NetAnalyzer下载地址 上篇我们回顾完了NetAnalyzer一些可有可无的历史,在本篇,我决定先不对NetAnalyzer做介绍,而是先要了解一些关于构建NetAnalyzer的基础知识,如系统中可以分析的一些网络协议,了解它们的分布方式,字段含义等.在了解了协议的基础上,开始对Winpcap进行一些介绍,在这过程中,学习配置Winpcap的开发环境,做一些简单的数据采集程序.第三部分着重介绍过滤表达式的一些基本语法结构.写下来则要

Memcached 二进制协议(BinaryProtocol) incr指令泄露内存数据的bug

缘起 最近有个分布式限速的需求.支付宝的接口双11只允许每秒调用10次. 单机的限速,自然是用google guava的RateLimiter. http://docs.guava-libraries.googlecode.com/git-history/master/javadoc/com/google/common/util/concurrent/RateLimiter.html 分布式的ReteLimiter,貌似没有现在的实现方案.不过用memcached或者Redis来实现一个简单的也

协议分析TMP

最近闲来有事, 分析了一个非常低端(非常低端的意思是说你不应该对她是否能取代你现有的QQ客户端作任何可能的奢望,她只是一个实验性的东西)的手机QQ的协议, 是手机QQ3.0,      所用到的TCP/HTTP通信协议版本是1.4, 也不知道是哪一年release的了, 至少有七八年的历久了吧, 反正就是: 功能非常弱! 主要的分析原因是想学学网络方面的编程经验(这是我第2次弄socket编程 :-) ), 以及学学怎么抓包分析. 主要用到的工具软件 手机QQ3.0: http://www.ru

[转载] TLS协议分析 与 现代加密通信协议设计

https://blog.helong.info/blog/2015/09/06/tls-protocol-analysis-and-crypto-protocol-design/?from=timeline&isappinstalled=0 最近发现密码学很有意思,刚好还和工作有点关系,就研究了一下,本文是其中一部分笔记和一些思考. 密码学理论艰深,概念繁多,本人知识水平有限,错误难免,如果您发现错误,请务必指出,非常感谢! 本文禁止转载 本文目标: 学习鉴赏TLS协议的设计,透彻理解原理和重

TCP/IP协议分析(推荐)

一;前言 学习过TCP/IP协议的人多有一种感觉,这东西太抽象了,没有什么数据实例,看完不久就忘了.本文将介绍一种直观的学习方法,利用协议分析工具学习TCP/IP,在学习的过程中能直观的看到数据的具体传输过程. 为了初学者更容易理解,本文将搭建一个最简单的网络环境,不包含子网. 二.试验环境 1.网络环境  如图1所示 图1 为了表述方便,下文中208号机即指地址为192.168.113.208的计算机,1号机指地址为192.168.113.1的计算机. 2.操作系统 两台机器都为Windows

【转】C# 串口操作系列(3) -- 协议篇,二进制协议数据解析

我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要关心我们的通讯协议如何缓存,分析,以及通知界面. 我们先说一下通讯协议.通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可能的简单以提高传输率,尽可能的具有安全性保证数据传输完整正确.基于这2点规则,我们一个通讯协议应该是这样的:头+数据长度+数据正文+校验 例如:AA 44 05 01 02 03 04 05 EA 这里我假设的一条数据,协议如下: 数据头:     AA 44 数据长度: 05 数据

[转] 用协议分析工具学习TCP/IP

一.前言 目前,网络的速度发展非常快,学习网络的人也越来越多,稍有网络常识的人都知道TCP/IP协议是网络的基础,是Internet的语言,可以说没有TCP/IP协议就没有互联网的今天.目前号称搞网的人非常多,许多人就是从一把夹线钳,一个测线器联网开始接触网络的,如果只是联网玩玩,知道几个Ping之类的命令就行了,如果想在网络上有更多的发展不管是黑道还是红道,必须要把TCP/IP协议搞的非常明白. 学习过TCP/IP协议的人多有一种感觉,这东西太抽象了,没有什么数据实例,看完不久就忘了.本文将介

C# 串口操作系列(3) -- 协议篇,二进制协议数据解析

C# 串口操作系列(3) -- 协议篇,二进制协议数据解析 标签: c#bufferobject通讯byte硬件驱动 2010-05-27 09:54 51565人阅读 评论(215) 收藏 举报  分类: 通讯类库设计(4)  版权声明:本文为博主原创文章,未经博主允许不得转载. 我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要关心我们的通讯协议如何缓存,分析,以及通知界面. 我们先说一下通讯协议.通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可