TCompactProtocol协议作为TBinaryProtocol协议的升级强化版,都作为二进制编码传输方式,采用了一种乐器MIDI文件的编码方法(wiki,百度下),简单介绍下两种思想:

1: ZigZag有符号数编码,如表格所示:

  编码前 编码后
0 0
-1 1
1 2
-2 3
2 4
-3 5

其效果等效于正数等于原先 * 2,负数变正数。

32bits int =  (i << 1) ^ (i >> 31), 64bits long = (l << 1) ^ (l >> 63)

:VLQ(variable-length quantity)编码:

           即一字节的最高位(MHB)为标志位,不参与具体的内容,意思数值的大小仅仅有其它七位来表示。当最高位bit为1时,表示下一个byte也是该数值的内容(下一个byte的低七位bits);当最高位bit为0时,下一个byte不参与其中。通过这样的方式,而不是int固定的4个bytes,long 8个bytes来讲,对于小数,能节约不少的空间大小;但凡事有利有弊,当数值比较大时,就要占用更多的空间,例如较大的int ,需要5bytes,较大的long需要10bytes.

   两者的结合 :

当VLQ编码遇到负数时,例如:long -1; 0XFFFFFFFFFFFFFFFF,就需要10bytes了,通过和ZigZag的结合,吧负数转变相应的正数。当正数,负数的 |数值|较小时,都可以通过两者的结合,有效的压缩占用的空间大小。但同上,数值较大不可避免的占用比平常正常编码更多的空间。

源码分析:

首先来看一下int32,long64的ZigZag编码:

  private long longToZigzag(long l) {
return (l << 1) ^ (l >> 63);
} /**
* Convert n into a zigzag int. This allows negative numbers to be
* represented compactly as a varint.
*/
private int intToZigZag(int n) {
return (n << 1) ^ (n >> 31);//正数 n << 1 扩大两倍 , n >> 31 = 0 , ^ 0 不变 ,2 * n ;
}

再看看int32,long64的varint写法:

 byte[] i32buf = new byte[5]; //int32 最大需要5个字节
private void writeVarint32(int n) throws TException {
int idx = 0; //index flag
while (true) {
if ((n & ~0x7F) == 0) { // if (n <= 2^7) 1byte
i32buf[idx++] = (byte)n;
// writeByteDirect((byte)n);
break;
// return;
} else {
i32buf[idx++] = (byte)((n & 0x7F) | 0x80); 、//else if(n > 2^ 7) 按小端方式给byte第八位贴上1标签,存放在buf。
// writeByteDirect((byte)((n & 0x7F) | 0x80));
n >>>= 7; //逻辑右移7bit,再次判断,loop
}
}
trans_.write(i32buf, 0, idx); //吧buf写入传输层
} /**
* Write an i64 as a varint. Results in 1-10 bytes on the wire.
*/
byte[] varint64out = new byte[10];//最大需要10bytes
private void writeVarint64(long n) throws TException {
int idx = 0;
while (true) {
if ((n & ~0x7FL) == 0) { //注意这边的 ~0x7FL(不能写成0x7F)
varint64out[idx++] = (byte)n;
break;
} else {
varint64out[idx++] = ((byte)((n & 0x7F) | 0x80));
n >>>= 7;
}
}
trans_.write(varint64out, 0, idx);
}

上面注解说明了varint的系统操作,预分配最大字节buffer,然后按照小端方式写入VLQ编码后实际内容。再来看看系统是怎么结合两者的:

 public void writeI32(int i32) throws TException {
writeVarint32(intToZigZag(i32)); //先调intToZigZag转换,在write VLQ。
} /**
* Write an i64 as a zigzag varint.
*/
public void writeI64(long i64) throws TException {
writeVarint64(longToZigzag(i64));
} public void writeI16(short i16) throws TException { //i16先按int32 zigzag编码转换 然后按VLQ转换
writeVarint32(intToZigZag(i16));
}

我们先系统的看一下TCompactProtocol按什么方法写入Thrift内部数据类型的,然后再看message的写法,一下是thrift内部数据类型,i16,i32,i64已经看完,在来看看别的:

  private static class Types {
public static final byte BOOLEAN_TRUE = 0x01;
public static final byte BOOLEAN_FALSE = 0x02;
public static final byte BYTE = 0x03;
public static final byte I16 = 0x04;
public static final byte I32 = 0x05;
public static final byte I64 = 0x06;
public static final byte DOUBLE = 0x07;
public static final byte BINARY = 0x08;
public static final byte LIST = 0x09;
public static final byte SET = 0x0A;
public static final byte MAP = 0x0B;
public static final byte STRUCT = 0x0C;
}

boolean:

  public void writeBool(boolean b) throws TException {
if (booleanField_ != null) {
// we haven't written the field header yet
writeFieldBeginInternal(booleanField_, b ? Types.BOOLEAN_TRUE : Types.BOOLEAN_FALSE);
booleanField_ = null;
} else {
// we're not part of a field, so just write the value.
writeByteDirect(b ? Types.BOOLEAN_TRUE : Types.BOOLEAN_FALSE);//按照上面对应的boolean_yes,boolean_no字节值写入。
}
}

TCompactProtocol写入Boolean分两种情况,1:该boolean值为TStruct中的内部成员时TField时,得写入header数据(即内容和数据类型压缩在一起写);2 :如果不为TField内部类型的话,直接按byte写入。关于TStruct和TField的细节请参照上篇

具体tstruct写入,稍后分析。

byte:

public void writeByte(byte b) throws TException {
writeByteDirect(b);//one byte 直接写入。
}
 private byte[] byteDirectBuffer = new byte[1];
private void writeByteDirect(byte b) throws TException {
byteDirectBuffer[0] = b;
trans_.write(byteDirectBuffer);
}

double:

  public void writeDouble(double dub) throws TException {
byte[] data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; //8个字节
fixedLongToBytes(Double.doubleToLongBits(dub), data, 0); //double 转long bit 分布,然后按照fix64编码传输。
trans_.write(data);
}
  private void fixedLongToBytes(long n, byte[] buf, int off) {
buf[off+0] = (byte)( n & 0xff);
buf[off+1] = (byte)((n >> 8 ) & 0xff);
buf[off+2] = (byte)((n >> 16) & 0xff);
buf[off+3] = (byte)((n >> 24) & 0xff);
buf[off+4] = (byte)((n >> 32) & 0xff);
buf[off+5] = (byte)((n >> 40) & 0xff);
buf[off+6] = (byte)((n >> 48) & 0xff);
buf[off+7] = (byte)((n >> 56) & 0xff);
}

可以看出double类型,先按Double.doubletoLongBits()转换后,按照fixed64编码写入(8字节小端写入),如上。

bytearray:

 public void writeBinary(ByteBuffer bin) throws TException {
int length = bin.limit() - bin.position();//计算数据len
writeBinary(bin.array(), bin.position() + bin.arrayOffset(), length);
}
private void writeBinary(byte[] buf, int offset, int length) throws TException {
writeVarint32(length); //按VLQ编码写入len值,这里没有使用zigzag编码(zigzag编码主要解决负数VLQ编码占用大空间的情况,这里len不为负,直接VLQ写入)
trans_.write(buf, offset, length);//写入实际内buff中内容
}

string:

 public void writeString(String str) throws TException {
try {
byte[] bytes = str.getBytes("UTF-8");//utf-8编码,得到字节数组
writeBinary(bytes, 0, bytes.length);//抵用writeBinary,see 上面
} catch (UnsupportedEncodingException e) {
throw new TException("UTF-8 not supported!");
}
}

容器类型:

SetTag:

public void writeSetBegin(TSet set) throws TException {
writeCollectionBegin(set.elemType, set.size);//set类型,长度值
}

type byte:

public final class TType {
public static final byte STOP = 0;
public static final byte VOID = 1;//java中没有这种类型,这里存在只是为了别的语言,可能
public static final byte BOOL = 2;
public static final byte BYTE = 3;
public static final byte DOUBLE = 4;
public static final byte I16 = 6;
public static final byte I32 = 8;
public static final byte I64 = 10;
public static final byte STRING = 11;
public static final byte STRUCT = 12;
public static final byte MAP = 13;
public static final byte SET = 14;
public static final byte LIST = 15;
public static final byte ENUM = 16;//低下static {}中,该类型也没用到。 所以4bits 够用了
}
 protected void writeCollectionBegin(byte elemType, int size) throws TException {
if (size <= 14) { // 1110
writeByteDirect(size << 4 | getCompactType(elemType));//size <= 14时,size << 4 | 对应的TTyte,压缩从一个byte写入。
} else {
writeByteDirect(0xf0 | getCompactType(elemType));// 1111 0000| ttype ,按one byte写入
writeVarint32(size);// VLQ编码写入len
}
}

getCompactType(xx):

 private byte getCompactType(byte ttype) {
return ttypeToCompactType[ttype];
}
static {
ttypeToCompactType[TType.STOP] = TType.STOP;
ttypeToCompactType[TType.BOOL] = Types.BOOLEAN_TRUE;
ttypeToCompactType[TType.BYTE] = Types.BYTE;
ttypeToCompactType[TType.I16] = Types.I16;
ttypeToCompactType[TType.I32] = Types.I32;
ttypeToCompactType[TType.I64] = Types.I64;
ttypeToCompactType[TType.DOUBLE] = Types.DOUBLE;
ttypeToCompactType[TType.STRING] = Types.BINARY;
ttypeToCompactType[TType.LIST] = Types.LIST;
ttypeToCompactType[TType.SET] = Types.SET;
ttypeToCompactType[TType.MAP] = Types.MAP;
ttypeToCompactType[TType.STRUCT] = Types.STRUCT;
}
public void writeListEnd() throws TException {} //no-op 空操作,走个形式而已

list tag:

 public void writeListBegin(TList list) throws TException {
writeCollectionBegin(list.elemType, list.size);
}
public void writeListEnd() throws TException {}

同上,就不重复了。

map tag:

public void writeMapBegin(TMap map) throws TException {
if (map.size == 0) {//size == 0
writeByteDirect(0); //直接写入one byte 0完事。
} else {
writeVarint32(map.size); //VLQ写入长度
writeByteDirect(getCompactType(map.keyType) << 4 | getCompactType(map.valueType)); //one byte 写入 keyType(TType),valueType(TType) (keyType << 4 | valueType) 与avro的map不同,其key
} //type只能为string类型。
}

wirteMapEnd()也是no-op操作就不贴了。

介绍完内置类型的写入方式,可以介绍写message了。

public void writeMessageBegin(TMessage message) throws TException {
writeByteDirect(PROTOCOL_ID); // 1000 0010 one byte protocol_id
writeByteDirect((VERSION & VERSION_MASK) | ((message.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK));// ((0000 0001 & 0001 1111) | (type << 5)) & 1110 0000); one byte高三位messageType |
writeVarint32(message.seqid); //低五位version bits, VLQ编码写入message 的sequence increment id.
writeString(message.name); //消息名,即方法名。
}
 private static final byte PROTOCOL_ID = (byte)0x82;//1000 0010
private static final byte VERSION = 1;
private static final byte VERSION_MASK = 0x1f; // 0001 1111
private static final byte TYPE_MASK = (byte)0xE0; // 1110 0000
private static final byte TYPE_BITS = 0x07; // 0000 0111
private static final int TYPE_SHIFT_AMOUNT = 5;

这里的version应该为了以后的version更新。byte类型的messageType(call, execption, oneway,reply)具体请见上篇TBinaryProtocol分析。为了发消息的完整性,还是贴出TServiceClient的sendBase()步骤:

 protected void sendBase(String methodName, TBase args) throws TException {
oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}

现在该进行TBASE的write()了,即方法参数和返回值的封装类写,还是以hello.thrift为例:

hellostring_args的write():

public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}

schema的write():

public void write(org.apache.thrift.protocol.TProtocol oprot, helloString_args struct) throws org.apache.thrift.TException {
struct.validate(); oprot.writeStructBegin(STRUCT_DESC);
if (struct.para != null) {
oprot.writeFieldBegin(PARA_FIELD_DESC);
oprot.writeString(struct.para);
oprot.writeFieldEnd();
}
oprot.writeFieldStop();
oprot.writeStructEnd();
}
 private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("helloString_args");// 方法参数封装类的TStruct表示。

 private static final org.apache.thrift.protocol.TField PARA_FIELD_DESC = new org.apache.thrift.protocol.TField("para", org.apache.thrift.protocol.TType.STRING, (short)1);

ok,此处的oprot为TCompactProtocol,看看他的writeStructBegin():

 public void writeStructBegin(TStruct struct) throws TException {
lastField_.push(lastFieldId_);记住上次write struct 最后的field id.
lastFieldId_ = 0; //从本次参数写开始。
}
private ShortStack lastField_ = new ShortStack(15); //用于存放Tstructs中的field id(也就是thrift定义文件中service方法参数的标号 1:,2:);用于跟踪当前struct或者之前struct的field id

接下来,写writeFieldBegin()吧:

 public void writeFieldBegin(TField field) throws TException {
if (field.type == TType.BOOL) { //如果该方法参数为boolean类型,
// we want to possibly include the value, so we'll wait.
booleanField_ = field; //这里先做下标记,等会和具体boolean值一块写,压缩嘛!一开始介绍些基本数据类型(上面)的boolean的两种情况,第一种指当boolean值为Tfield的话,压缩一下,跟这里相结合,
} else { //这里先记录下header metadata,等写实际内容时,即writeBoolean在一块写。
writeFieldBeginInternal(field, (byte)-1);
}
}
private void writeFieldBeginInternal(TField field, byte typeOverride) throws TException {
// short lastField = lastField_.pop(); // if there's a type override, use that. // -1获得其内置数据类型,如果非-1情况,(指的是boolean)直接写入其byte值 ,true 0x01,false 0x02
byte typeToWrite = typeOverride == -1 ? getCompactType(field.type) : typeOverride; // typeOverride为写Boolean值,特设的,对其优化,one byte写入 // check if we can use delta encoding for the field id 增量式编码前提,用one byte 4MSB来做增量式编码,所有field id之间的差不能大于15.每次写Tstruct(即一个方法参数的封装类,其中可能含有很多参数)
if (field.id > lastFieldId_ && field.id - lastFieldId_ <= 15) { // 因为每次写struct时,都会设置last_fieldid_ = 0,所以都是一次方法RPC调用参数表示ID之间的比较。不会出现上次RPC方法调用的参数id和
// write them together                      //本次RPC方法调用参数id的比较。 
writeByteDirect((field.id - lastFieldId_) << 4 | typeToWrite);  //本次field id和上次field id做增量 << 4和复写标志做 |,用一个byte传输,压缩空间。
} else {
// write them separate
writeByteDirect(typeToWrite); //分开写 one byte 复写标志。
writeI16(field.id); //i16 (zigzag + vlq编码)写入,参数个数最大2^16个。
} lastFieldId_ = field.id; //重新复制lastfield_id
// lastField_.push(field.id);
}

然后就是写具体的参数值内容了,写完后写上writeFieldEnd()操作;

structs所有的参数都写完后,调用writeFieldStop():

 public void writeFieldStop() throws TException {
writeByteDirect(TType.STOP);// one byte value 0,占位符吧,标志读完了。
}

writeStructEnd():

  public void writeStructEnd() throws TException {
lastFieldId_ = lastField_.pop();//重新写structs时,会吧这值压入stack,并重新附上0.
}
public void writeMessageEnd() throws TException {}

读操作就不分析了,朋友们可以参照了去看看。

Thrift之TProtocol系列TCompactProtocol解析的更多相关文章

  1. Thrift之TProtocol系列TBinaryProtocol解析

    首先看一下Thrift的整体架构,如下图: 如图所示,黄色部分是用户实现的业务逻辑,褐色部分是根据thrift定义的服务接口描述文件生成的客户端和服务器端代码框架(前篇2中已分析了thrift ser ...

  2. Thrift之TProtocol系列TJSONProtocol解析

    在了解JSON协议之前,朋友们可以先去了解一下JSON的基础知识,和ASCII基本分布,关于JSON一些常识请见这里; JSON (JavaScript Object Notation)是一种数据交换 ...

  3. XML系列之--解析电文格式的XML(二)

    上一节介绍了XML的结构以及如何创建.讲到了XML可作为一种简单文本存储数据,把数据存储起来,以XML的方式进行传递.当接收到XML时,必不可少的就是对其进行解析,捞取有效数据,或者将第三方数据以节点 ...

  4. [转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

    来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)—— ...

  5. junit4X系列--Runner解析

    前面我整理了junit38系列的源码,那junit4X核心代码也基本类似.这里我先转载一些关于junit4X源码解析的好文章.感谢原作者的分享.原文地址:http://www.blogjava.net ...

  6. iOS开发系列-JSON解析

    概述 JOSN是一种轻量级的数据格式,一般用于数据交互.服务器返回给客户端,一般都是JSON格式或者XML格式. JSON的格式: {"name" : "CoderHon ...

  7. Thrift compiler代码生成类解析

    代码生成类解析: Thrift--facebook RPC框架,介绍就不说了,百度,google一大把,使用也不介绍,直接上结构和分析吧. Hello.thrift文件内容如下: namespace ...

  8. Thrift源码解析--TBinaryProtocol

    本文为原创,未经许可禁止转载. 关于Tprotocol层都是一些通信协议,个人感觉内容较大,很难分类描述清楚.故打算以TBinaryProtocol为例,分析客户端发请求以及接收服务端返回数据的整个过 ...

  9. thrift系列 - 快速入门

    1.简介           Thrift是当前流行的RPC框架之一,它有强大的代码生成引擎,可以跨语言,轻松解决程序间的通信问题. 本文旨在帮助大家快速入门,若想深入原理,请参见thrift官网:h ...

随机推荐

  1. 线程安全Dictionary

    public abstract class ReadFreeCache<TKey, TValue> { protected ReadFreeCache() : this(null) { } ...

  2. 项目实战10.1—企业级自动化运维工具应用实战-ansible

    实战环境: 公司计划在年底做一次大型市场促销活动,全面冲刺下交易额,为明年的上市做准备.公司要求各业务组对年底大促做准备,运维部要求所有业务容量进行三倍的扩容,并搭建出多套环境可以共开发和测试人员做测 ...

  3. php echo和print_r和var_dump的区别

    echo -- 适合打印单数据 整型 字符串 浮点型 print_r -- 适合打印符合数据 数组 资源 对象 var_dump -- 适合调试变量打印特许的类型 如BOOL NULL 不仅能把值打印 ...

  4. 【Python3之模块及包的导入】

    一.模块导入 1.定义 Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块让你能够有逻辑地组织你的 Python ...

  5. RGB颜色 对照表

      来自为知笔记(Wiz)

  6. QMAKESPEC环境变量详解

    相关知识 要讲解QMAKESPEC环境变量的知识,先要了解如下知识 qmake .pro项目文件 makefile文件 1.qmake qmake是用来为不同的平台的开发项目创建Makefile的Tr ...

  7. BCB F12切换界面 显示异常

      亲们,我偶遇了一个小怪兽F12切换界面,效果如下:       还没有解决办法:

  8. SpringMVC handleMapping 处理器映射器 属性清单

    映射器的属性清单 defaultHandler         在映射与所有处理器都不匹配的情况下,指定默认的处理器(处理器即你定义的Controller(action)类) order        ...

  9. 解决linux 乌班图下使用eclipse创建类和其他各种操作进程卡死的问题的一种可能方法

    [转载]http://blog.csdn.net/u010652906/article/details/51626257Eclipse设置面板菜单可以点,左边面板不出现,创建类.创建包都会卡死的问题, ...

  10. dataZoom 详细参数

    dataZoom:[ //区域缩放 { id: 'dataZoomX', show:true, //是否显示 组件.如果设置为 false,不会显示,但是数据过滤的功能还存在. backgroundC ...