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 {}

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

最新文章

  1. ASP.NET MVC5+EF6+EasyUI 后台管理系统(73)-微信公众平台开发-消息管理
  2. UOJ25——IOI2014Wall
  3. HT图形组件设计之道(一)
  4. 04SpringMvc_映射器_BeanNameUrlHanderMapping
  5. win7下搭建opengles2.0编程环境
  6. UVA12304 2D Geometry 110 in 1! 计算几何
  7. HDU 5543 Pick The Sticks:01背包变种
  8. DIV+CSS架构网站的7种版面布局形式
  9. http://codeforces.com/problemset/problem/545/D
  10. Jquery基础知识01
  11. 微服务与SOA的区别
  12. vue性能
  13. Spring中RedirectAttributes的用法
  14. PID算法控制简单理解
  15. software download
  16. Windows下如何查看某个端口被谁占用
  17. 【Vue】浅谈Vue不同场景下组件间的数据交流
  18. Android 隐藏、显示软键盘方法
  19. 本地搭建https服务
  20. vue组件 订单支付15分钟倒计时

热门文章

  1. Shell脚本实现文件遍历和删除操作
  2. micropython TPYBoard v202 超声波测距
  3. iconfont-字体图标
  4. css-display
  5. 腾讯云负载均衡CLB的那些“独门利器”
  6. 做了一个web版的 MyBatis Generator
  7. Sampling
  8. [PHP] 编译构建最新版PHP源码
  9. virtio 简介
  10. Android自定义安全键盘