首先看一下Thrift的整体架构,如下图:

如图所示,黄色部分是用户实现的业务逻辑,褐色部分是根据thrift定义的服务接口描述文件生成的客户端和服务器端代码框架(前篇2中已分析了thrift service生成代码),红色部分是根据Thrift文件生成代码实现数据的读写操作。红色部分以下是Thrift的协议,传输体系以及底层的IO通信,使用thrift可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码(Thrift提供的是一种大而全的服务,它认为没有统一的标准,用户根据自己需要,组合使用之;而avro排斥多方案引起的混乱,提倡建立统一的标准,后篇在来分析avro,其作为hadoop rpc框架,在大数据量传输方面有一定的优势。)。

数据类型:

      Thrift可定义的数据类型包括以下几种(说句后话,为什么这些框架protobuf, thrift, avro都定义自己的数据类型???他们都作为多语言支持的,内部类型的定义,不同语言支持的数据类型统一映射到框架内部支持数据类型,方便处理,在数据读写传输过程中按统一方式处理。)

  • 基本类型
  1. bool    : 布尔,true or false,对应java的boolean
  2. byte    : 8位有符号整数,对应java的byte
  3. i16      : 16位有符号整数,对应java的short
  4. i32      : 32位有符号整数,对应java的int
  5. i64      : 64位有符号整数,对应java的long
  6. double : 64位浮点数     ,对应java的double
  7. string  : 未知编码文本或二进制字符串,对应java的string
  • 结构体类型
  1. struct : 定义公共对象,类似于C预压中的结构体定义,在java中是一个javabean.
  • 容器类型
  1. list  : 对应java的arraylist
  2. set  : 对应java的hashset
  3. map: 对应java的HashMap
  • 异常类型 (在java中,TException为基类)
  • 服务类型 (在Java中,统一为Iface, AsyncIface接口)

协议:

Thrift可以让用户选择客户端和服务器端之间的传输通信协议的类别(用户不同的需求,不同应用可以根据自己需求选择适合自己的传输协议),一般情况下使用二进制类型的传输协议(提高传输效率,多数用于内部系统之间的通信传输),还可以使用基于文本类型的协议(json),json,xml作为通用网络数据传输协议,可以实现外部系统调用。

  • TBinaryProtocol- 二进制编码格式进行数据传输(默认)
  • TCompactProtocol- 高效率,密集的二进制编码格式进行数据传输(了解protocol buffer内部编码实现的话,就不足为奇了)
  • TJSONProtocol - 使用JSON的数据编码协议进行数据传输。
  • TSimpleJSONProtocol- 只提供JSON只写的协议,使用与通过脚本语言解析

其中TProtocolDecorator,装饰者,抽象类,其中典型实现TMultiplexedProtocol,允许客户端连接多功能server.

TBinaryProtocol:

该协议作为thrift默认的二进制协议,通过它,所有数据都是以二进制形式读写,没有什么特殊处理,除了tag外,基本都是数据本身的二进制,不过值得了解的是Thrift的读写message的过程(tag的运用);

这里列出协议抽象基类TProtocol的一部分方法,可以看出各种tagBeging(),tagEnd()方法,read方法一样。

还是以上篇Thrift 代码生成分析篇解析(Hello.thrift)开始,看一下客户端调用service方法开始引入TBinaryProtocol,没看过的朋友可以先了解一下。方法如下:

  string helloString(1:string para)

进Thrift为我们生成的Hello类里面看看吧。

   public String helloString(String para) throws org.apache.thrift.TException
{
send_helloString(para);
return recv_helloString();
} public void send_helloString(String para) throws org.apache.thrift.TException
{
helloString_args args = new helloString_args();
args.setPara(para);
sendBase("helloString", args);
}

helloString_args上篇分析过,直接进其父类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();
}

协议层先写入messageBeginTag,然后写message(对应其方法参数的封装类) ,最后messageEndTag,传输层flush。再来看下message的结构吧。

public final class TMessage {
public TMessage() {
this("", TType.STOP, 0);//占位符,1byte,没实际内容
} public TMessage(String n, byte t, int s) {
name = n; //方法名
type = t; //消息类型
seqid = s; //消息 seq number
} public final String name;
public final byte type;
public final int seqid; @Override
public String toString() {
return "<TMessage name:'" + name + "' type: " + type + " seqid:" + seqid + ">";
}

Tmessage三个成员,RPC调用方法名,消息类型,消息递增序列化。接着看下消息类型:

public final class TMessageType {
public static final byte CALL = 1;
public static final byte REPLY = 2;
public static final byte EXCEPTION = 3;
public static final byte ONEWAY = 4;
}

四种消息类型,RPC request(客户端请求),RPC正常repsonse(服务器响应),RPC exception(服务器端返回异常),单向RPC(客户端发出request,但不要求服务器端给出响应).

<****************************************************************************************************************************************>

进入TBinaryProtocol中的writeMessageBegin()瞧瞧:

  public void writeMessageBegin(TMessage message) throws TException {
if (strictWrite_) { //是否严格写
int version = VERSION_1 | message.type; //版本号,消息类型。
writeI32(version);
writeString(message.name); //消息name属性,即方法名。
writeI32(message.seqid);//序列号
} else { //非严格写,无版本号和消息类型
writeString(message.name);
writeByte(message.type);
writeI32(message.seqid);
}
}

版本号如下:

  protected static final int VERSION_MASK = 0xffff0000;
protected static final int VERSION_1 = 0x80010000;

继续writeI32():

  private byte[] i32out = new byte[4];
public void writeI32(int i32) throws TException {
i32out[0] = (byte)(0xff & (i32 >> 24));
i32out[1] = (byte)(0xff & (i32 >> 16));
i32out[2] = (byte)(0xff & (i32 >> 8));
i32out[3] = (byte)(0xff & (i32));
trans_.write(i32out, 0, 4);
}

大端写入int的字节数组。writeString():

 public void writeString(String str) throws TException {
try {
byte[] dat = str.getBytes("UTF-8");
writeI32(dat.length);
trans_.write(dat, 0, dat.length);
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}

string进UTF-8后获得其字节数组,写入数组长度,在写string bytes,(string写,统一utf-8编码后,先写其字节数组长度,再写实际内容)。再来看一下写实际消息:

 args.write(oprot_); // TServiceClient中的write,调用生成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,上篇有分析
}
 private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
static {
schemes.put(StandardScheme.class, new helloString_argsStandardSchemeFactory());
schemes.put(TupleScheme.class, new helloString_argsTupleSchemeFactory());
}
private static class helloString_argsStandardScheme extends StandardScheme<helloString_args> {
public void write(org.apache.thrift.protocol.TProtocol oprot, helloString_args struct) throws org.apache.thrift.TException {
struct.validate(); oprot.writeStructBegin(STRUCT_DESC); //先写structbeginTag.
if (struct.para != null) { //struct中参数不为null
oprot.writeFieldBegin(PARA_FIELD_DESC); //写fieldbegingTag
oprot.writeString(struct.para); //写参数
oprot.writeFieldEnd(); //写fieldEndTag
}
oprot.writeFieldStop();//写fieldStopTag(猜测应该是当远程RPC调用方法中有多个参数时,用于标记所有参数写完标志Tag,fieldEndtag只代表每个参数写完,因为本例就一个参数不好验证,朋友确定的话,不吝赐教)
oprot.writeStructEnd(); //写structEndTag(方法参数在Thrift中被视为struct结构,即java中javabean,其中成员为具体方法参数值。方法返回值也一样。)
} }

上面注解可以了解大概步骤:

    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("helloString_args");

    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);

TStruct结构再来瞧瞧:

public final class TStruct {
public TStruct() {
this("");
} public TStruct(String n) {
name = n;
} public final String name;
}

简单的string属性,struct名,即自动生成的代码类名。再瞅瞅TField吧:

public class TField {
public TField() {
this("", TType.STOP, (short)0);//空成员,没赋值的情况。
} public TField(String n, byte t, short i) {
name = n; //RPC方法调用参数名
type = t; //参数类型
id = i;//thrift文件定义的参数顺序
} public final String name;
public final byte type;
public final short id; public String toString() {
return "<TField name:'" + name + "' type:" + type + " field-id:" + id + ">";
}
public final class TType { //thrift内部数据类型
public static final byte STOP = 0;
public static final byte VOID = 1;
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;
}

OK,继续跳回TBinaryProtocol中,跳来跳去的,大家有点累了吧,坚持就是胜利^!^

  public void writeMessageEnd() {}

  public void writeStructBegin(TStruct struct) {}

  public void writeStructEnd() {}

fuck,这三个为空操作,蛋蛋伤。

 public void writeFieldBegin(TField field) throws TException {
writeByte(field.type);
writeI16(field.id);
} public void writeFieldEnd() {}

fieldBeginTag中,先写入参数类型,参数序列号。至此消息写完毕(读操作就不讲了,反操作,差不多,不过异步操作,准备后面单独开一篇来讲下),我们再来看看TBinaryProtocol中其他方法:

boolean:一个字节1或0.

public void writeBool(boolean b) throws TException {
writeByte(b ? (byte)1 : (byte)0); //一个字节
}

i16,i64:依旧大端。

 public void writeI16(short i16) throws TException { //2字节
i16out[0] = (byte)(0xff & (i16 >> 8));
i16out[1] = (byte)(0xff & (i16));
trans_.write(i16out, 0, 2);
} private byte[] i64out = new byte[8];
public void writeI64(long i64) throws TException {//8字节
i64out[0] = (byte)(0xff & (i64 >> 56));
i64out[1] = (byte)(0xff & (i64 >> 48));
i64out[2] = (byte)(0xff & (i64 >> 40));
i64out[3] = (byte)(0xff & (i64 >> 32));
i64out[4] = (byte)(0xff & (i64 >> 24));
i64out[5] = (byte)(0xff & (i64 >> 16));
i64out[6] = (byte)(0xff & (i64 >> 8));
i64out[7] = (byte)(0xff & (i64));
trans_.write(i64out, 0, 8);
}

double:先转化为long字节分布,然后按I64写,(没有float哦!):

public void writeDouble(double dub) throws TException {
writeI64(Double.doubleToLongBits(dub));
}

Map tag:先写map key类型(1字节),然后map value类型(1字节),最后写键值对长度(4字节),扯句后话,不想avro中的map,其key type只能为string.

public void writeMapBegin(TMap map) throws TException {
writeByte(map.keyType);
writeByte(map.valueType);
writeI32(map.size);
} public void writeMapEnd() {}

List Tag: 先写list 值类型(1字节),在写list长度(4字节)。

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

set Tag: 同上。

 public void writeSetBegin(TSet set) throws TException {
writeByte(set.elemType);
writeI32(set.size);
} public void writeSetEnd() {}

read操作就不细谈了,朋友们可以自己去看看。

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

  1. Thrift之TProtocol系列TCompactProtocol解析

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

  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源码解析--TBinaryProtocol

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

  8. Thrift compiler代码生成类解析

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

  9. thrift系列 - 快速入门

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

随机推荐

  1. PHP常用功能模块

    错误异常模块 错误处理 1. 系统定义了一些二进制码,用来表示错误报告的级别:     在 /etc/php5/apache2/php.ini中修改php配置文件,其中display_errors默认 ...

  2. Java笔记:开发环境

    Java开发环境 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称.由James Gosling和同事们共同研发,并在1995年正 ...

  3. SQL Server 审计操作概念

    概述 对于一般的数据库系统审计可能不太会被重视,但是对于金融系统就不一样的.金融系统对审计要求会很高,除了了记录数据库各种操作记录还可能会需要开发报表来呈现这些行为数据.使用SQL Server Au ...

  4. 推荐!手把手教你使用Git(转载)

    转载地址http://blog.jobbole.com/78960/,涂根华的博客. Git基本常用命令如下: mkdir:         XX (创建一个空目录 XX指目录名) pwd:      ...

  5. 【转】WEB测试要点总结

    一.输入框 1.字符型输入框: (1)字符型输入框:英文全角.英文半角.数字.空或者空格.特殊字符"~!@# ¥%--&*?[]{}"特别要注意单引号和&符号.禁止 ...

  6. [笔记]《JavaScript高级程序设计》- JavaScript简介

    JavaScript实现 虽然JavaScript和ECMAScript通常都被人们用来表达相同的含义,但JavaScript的含义却比ECMA-262中规定的要多得多.一个完整的JavaScript ...

  7. python之 正则表达式

    简介 Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式.Python 1.5之前版本则是通过 regex 模块提供 Emacs 风格的模式.Emacs 风格模式可读 ...

  8. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Django基础学习笔记

    Django开发流程 创建一个django项目:命令:django-admin startproject 项目名 进入到项目并创建一个应用:命令:python manage.py startapp 应 ...

  10. DFS中的奇偶剪枝学习笔记

    奇偶剪枝学习笔记 描述 编辑 现假设起点为(sx,sy),终点为(ex,ey),给定t步恰好走到终点, s | | | + — — — e 如图所示(“|”竖走,“—”横走,“+”转弯),易证abs( ...