Thrift之TProtocol系列TBinaryProtocol解析
首先看一下Thrift的整体架构,如下图:
如图所示,黄色部分是用户实现的业务逻辑,褐色部分是根据thrift定义的服务接口描述文件生成的客户端和服务器端代码框架(前篇2中已分析了thrift service生成代码),红色部分是根据Thrift文件生成代码实现数据的读写操作。红色部分以下是Thrift的协议,传输体系以及底层的IO通信,使用thrift可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码(Thrift提供的是一种大而全的服务,它认为没有统一的标准,用户根据自己需要,组合使用之;而avro排斥多方案引起的混乱,提倡建立统一的标准,后篇在来分析avro,其作为hadoop rpc框架,在大数据量传输方面有一定的优势。)。
数据类型:
Thrift可定义的数据类型包括以下几种(说句后话,为什么这些框架protobuf, thrift, avro都定义自己的数据类型???他们都作为多语言支持的,内部类型的定义,不同语言支持的数据类型统一映射到框架内部支持数据类型,方便处理,在数据读写传输过程中按统一方式处理。)
- 基本类型
- bool : 布尔,true or false,对应java的boolean
- byte : 8位有符号整数,对应java的byte
- i16 : 16位有符号整数,对应java的short
- i32 : 32位有符号整数,对应java的int
- i64 : 64位有符号整数,对应java的long
- double : 64位浮点数 ,对应java的double
- string : 未知编码文本或二进制字符串,对应java的string
- 结构体类型
- struct : 定义公共对象,类似于C预压中的结构体定义,在java中是一个javabean.
- 容器类型
- list : 对应java的arraylist
- set : 对应java的hashset
- 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解析的更多相关文章
- Thrift之TProtocol系列TCompactProtocol解析
TCompactProtocol协议作为TBinaryProtocol协议的升级强化版,都作为二进制编码传输方式,采用了一种乐器MIDI文件的编码方法(wiki,百度下),简单介绍下两种思想: 1: ...
- Thrift之TProtocol系列TJSONProtocol解析
在了解JSON协议之前,朋友们可以先去了解一下JSON的基础知识,和ASCII基本分布,关于JSON一些常识请见这里; JSON (JavaScript Object Notation)是一种数据交换 ...
- XML系列之--解析电文格式的XML(二)
上一节介绍了XML的结构以及如何创建.讲到了XML可作为一种简单文本存储数据,把数据存储起来,以XML的方式进行传递.当接收到XML时,必不可少的就是对其进行解析,捞取有效数据,或者将第三方数据以节点 ...
- [转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)
来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)—— ...
- junit4X系列--Runner解析
前面我整理了junit38系列的源码,那junit4X核心代码也基本类似.这里我先转载一些关于junit4X源码解析的好文章.感谢原作者的分享.原文地址:http://www.blogjava.net ...
- iOS开发系列-JSON解析
概述 JOSN是一种轻量级的数据格式,一般用于数据交互.服务器返回给客户端,一般都是JSON格式或者XML格式. JSON的格式: {"name" : "CoderHon ...
- Thrift源码解析--TBinaryProtocol
本文为原创,未经许可禁止转载. 关于Tprotocol层都是一些通信协议,个人感觉内容较大,很难分类描述清楚.故打算以TBinaryProtocol为例,分析客户端发请求以及接收服务端返回数据的整个过 ...
- Thrift compiler代码生成类解析
代码生成类解析: Thrift--facebook RPC框架,介绍就不说了,百度,google一大把,使用也不介绍,直接上结构和分析吧. Hello.thrift文件内容如下: namespace ...
- thrift系列 - 快速入门
1.简介 Thrift是当前流行的RPC框架之一,它有强大的代码生成引擎,可以跨语言,轻松解决程序间的通信问题. 本文旨在帮助大家快速入门,若想深入原理,请参见thrift官网:h ...
随机推荐
- linux下查看系统属性
inux下查看系统属性1.查看cpu信息查看所有cpu信息:cat /proc/cpuinfo查看cpu类型: grep "model name" /proc/cpuinfo2.查 ...
- 【精选】Nginx模块Lua-Nginx-Module学习笔记(一)Nginx Lua API 接口详解
源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一.介绍 各种* _by_lua,* _by_lua_block和* _by_lua_file配置指令用 ...
- redmine 安装(Centos 6.5 x64)
参考:http://www.linuxidc.com/Linux/2015-03/115545.htm 平台搭建 1)基础环境 yum -y install libyaml-devel zlib-de ...
- 建造者模式(Builder)-宏观的使用角度
建造者模式(Builder) 建造者模式是用来解决产品对象的创建过程是由多个零件组成的情况,这些零件与产品本身是组合关系,也就是部分与整体,这些零件的创建顺序,还有一些创建中的逻辑,都是稳定的,可以封 ...
- SSM框架开发web项目系列(六) SpringMVC入门
前言 我们最初的javaSE部分学习后,基本算是入门了,也熟悉了Java的语法和一些常用API,然后再深入到数据库操作.WEB程序开发,渐渐会接触到JDBC.Servlet/Jsp之类的知识,期间可能 ...
- OpenCV探索之路(四):膨胀、腐蚀、开闭运算
腐蚀和膨胀是最基本的形态学运算. 腐蚀和膨胀是针对白色部分(高亮部分)而言的. 膨胀就是对图像高亮部分进行"领域扩张",效果图拥有比原图更大的高亮区域:腐蚀是原图中的高亮区域被蚕食 ...
- js对象,set和get方法 的三种实现形式
var obj1 = { name: 'shaanxi', get nameGet() { return this.name + 'a'; }, set nameSet(name) { this.na ...
- Friday for Oldboy
计算机的硬件介绍 1. CPU的工作流程:取指令->解码->执行 . 程序状态字寄存器(Program Status Word,PSW)中有一个二进制位控制这两种模式. 内核态:当cp ...
- Axios使用说明
vue更新到2.0之后,作者就宣告不再对vue-resource更新,而是推荐的axios,前一段时间用了一下,现在说一下它的基本用法. 首先就是引入axios,如果你使用es6,只需要安装axios ...
- python子域名收集器
今天心血来潮做了一个子域名收集器.过程是蛋疼啊!这里先感谢一下qpython群的咸鱼大佬,在换页的时候出了点毛病,讲到后面我们就知道了. 思路: 代码开始: 我们要用到的模块是 Requests Bs ...