本文为原创,未经许可禁止转载。

关于Tprotocol层都是一些通信协议,个人感觉内容较大,很难分类描述清楚。故打算以TBinaryProtocol为例,分析客户端发请求以及接收服务端返回数据的整个过程。

先将客户端的测试用例贴上。

 public class DemoClient {
public static void main(String[] args) throws Exception{
String param1 = "haha";
Map<String, String> param3 = new HashMap<String, String>();
param3.put("1", "2");
Parameter param2 = new Parameter(10, "kaka"); TSocket socket = new TSocket("127.0.0.1", 7911);
socket.setTimeout(3000);
TTransport transport = socket;
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
DemoService.Client client = new DemoService.Client.Factory().getClient(protocol);
int result = client.demoMethod(param1, param2, param3);
System.out.println("result: " + result);
transport.close();
}

首先就是构造transport,这里由于TSocket extens TIOStreamTransport,因此可构造一个TSocket即可,而TSocket包含:host(主机IP),port(端口号),time_out(超时时间)与一个Socket。

  public TSocket(String host, int port, int timeout) {
host_ = host;
port_ = port;
timeout_ = timeout;
initSocket();
}

对于socket.setTimeout(3000);实际操作就是为TSocket中的socket设置timeout

  public void setTimeout(int timeout) {
timeout_ = timeout;
try {
socket_.setSoTimeout(timeout);
} catch (SocketException sx) {
LOGGER.warn("Could not set socket timeout.", sx);
}
}

下图是构造的transport直观构造:包含了host,inputStream,outputStream,port,socket,timeout.

transport.open所做的事情就是初始化一些输入输出流并且connect the socket to the InetSocketAddress

 /**
* Connects the socket, creating a new socket object if necessary.
*/
public void open() throws TTransportException {
if (isOpen()) {
throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected.");
} if (host_.length() == 0) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");
}
if (port_ <= 0) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open without port.");
} if (socket_ == null) {
initSocket();
} try {
socket_.connect(new InetSocketAddress(host_, port_), timeout_);
inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);//均采用缓冲模式输入输出流
outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);
} catch (IOException iox) {
close();
throw new TTransportException(TTransportException.NOT_OPEN, iox);
}
}

再看一下open之后的transport:

接下来就是在已有transport也就是TSocket的基础之上,完成Tprotocol的构建,这里选择了TBinaryProtocol。这个工作实际上就是将上一步建好的Ttransport关联到Tprotocol上来。相当于进一步封装。

 public abstract class TProtocol {

   /**
* Prevent direct instantiation
*/
@SuppressWarnings("unused")
private TProtocol() {} /**
* Transport
*/
protected TTransport trans_; /**
* Constructor
*/
protected TProtocol(TTransport trans) {
trans_ = trans;
} /**
* Transport accessor
*/
public TTransport getTransport() {
return trans_;
}
/**各种读写方法略去
*/
}

从TProtocol的构造方法中可以看出,实际上就是将上一步生成的Transport赋与TProtocol中的trans_变量并将strictRead_与strictWrite_赋值。

  /**
* Constructor
*/
public TBinaryProtocol(TTransport trans) {
this(trans, false, true);
} public TBinaryProtocol(TTransport trans, boolean strictRead, boolean strictWrite) {
super(trans);
strictRead_ = strictRead;
strictWrite_ = strictWrite;
}

其中还有一些字节数组的初始化工作。

 private byte [] bout = new byte[1];

  private byte[] i16out = new byte[2];

  private byte[] i32out = new byte[4];

  private byte[] i64out = new byte[8];
   

这时候一切准备就绪。Tprotocol目前状态如下图:

Tprotocol已经准备就绪,接下来的工作就是new 一个client,然后才可以去与服务端进行请求与响应。下面我把一个client的代码全部粘贴出来。

 public static class Client extends org.apache.thrift.TServiceClient implements Iface {
public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
public Factory() {}
public Client getClient(org.apache.thrift.protocol.TProtocol prot) {//通过Tprotocol去构造client
return new Client(prot);
}
public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
return new Client(iprot, oprot);
}
} public Client(org.apache.thrift.protocol.TProtocol prot)
{
super(prot, prot);//使用了相同的Tprotocol进行构造
} public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
super(iprot, oprot);
} public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException
{
send_demoMethod(param1, param2, param3);
return recv_demoMethod();
} public void send_demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException
{
demoMethod_args args = new demoMethod_args();
args.setParam1(param1);
args.setParam2(param2);
args.setParam3(param3);
sendBase("demoMethod", args);
} public int recv_demoMethod() throws org.apache.thrift.TException
{
demoMethod_result result = new demoMethod_result();
receiveBase(result, "demoMethod");
if (result.isSetSuccess()) {
return result.success;
}
throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "demoMethod failed: unknown result");
} }

为了理解客户端构造的具体过程,我把TserviceClient.class的部分源码贴出来:

  public TServiceClient(TProtocol iprot, TProtocol oprot) {
iprot_ = iprot;
oprot_ = oprot;
} protected TProtocol iprot_;
protected TProtocol oprot_; protected int seqid_; /**
* Get the TProtocol being used as the input (read) protocol.
* @return the TProtocol being used as the input (read) protocol.
*/
public TProtocol getInputProtocol() {
return this.iprot_;
} /**
* Get the TProtocol being used as the output (write) protocol.
* @return the TProtocol being used as the output (write) protocol.
*/
public TProtocol getOutputProtocol() {
return this.oprot_;
}

明显的可以看到,client有三个变量,TProtocol类型的iprot_和oprot_,还有一个顺序号seqid_.由于在构造client的过程中使用了相同的Tprotocol,在这里也就是使用了相同的TBinaryProtocol,因此iprot_与oprot_是相同的,都指向上一步生成的TProtocol,也就是TBinaryProtocol.当DemoService.Client client = new DemoService.Client.Factory().getClient(protocol);执行完毕后,client的状态如下图:

client已经准备完毕,我们调用client的方法就可以向服务端发送请求了。而这个过程的总体代码也就那么一点点,先直接贴出来:

   public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException
{
send_demoMethod(param1, param2, param3);//发送请求
return recv_demoMethod();//接收响应
} public void send_demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException
{
demoMethod_args args = new demoMethod_args();//封装请求参数demoMethod_args
args.setParam1(param1);
args.setParam2(param2);
args.setParam3(param3);
sendBase("demoMethod", args);//发请求
} public int recv_demoMethod() throws org.apache.thrift.TException
{
demoMethod_result result = new demoMethod_result();//封装接收响应数据demoMethod_result,貌似与demoMethod_args还不一样
receiveBase(result, "demoMethod");//接收返回数据
if (result.isSetSuccess()) {
return result.success;
}
throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "demoMethod failed: unknown result");
}

当执行完demoMethod_args args = new demoMethod_args();之后,其实就是对demoMethod_args中的静态变量进行了初始化,STRUCT_DESC,PARAM1_FIELD_DESC,PARAM2_FIELD_DESC,schemes,PARAM3_FIELD_DESC,metaDataMap等都有了初始值。args.setParam之后,demoMethod_args的状态:

接下来就是:

 protected void sendBase(String methodName, TBase args) throws TException {
oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));//注意这里的++seqid,就是发送请求的序号,递增
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();//这里最终其实就是outputStream进行flush
}

将methodName: demoMethod, args: demoMethod_args(param1:haha, param2:Parameter(id:10, name:kaka), param3:{1=2})写入Tprotocol,在这里是oprot_。

  public void writeMessageBegin(TMessage message) throws TException {
if (strictWrite_) {
int version = VERSION_1 | message.type;//异或形成版本号
writeI32(version);//写入版本号
writeString(message.name);//写方法名
writeI32(message.seqid);//方法序号
} else {
writeString(message.name);
writeByte(message.type);
writeI32(message.seqid);
}
}
  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");
}
}
  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);
}
  /**
* Writes to the underlying output stream if not null.
*/
public void write(byte[] buf, int off, int len) throws TTransportException {
if (outputStream_ == null) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot write to null outputStream");
}
try {
outputStream_.write(buf, off, len);
} catch (IOException iox) {
throw new TTransportException(TTransportException.UNKNOWN, iox);
}
}

从以上代码可以看出来,无论怎么写,都是一层层深入的,TProtocol oprot_ ----->Ttransport trans_ ----->OutputStream outputStream(TODO:这里的outputStream其实也是bufferedOutputStream,也就是刚刚初始化transport的时候那个outputstream.其中比较奇葩的是args_.write,其代码如下,最后还是绕到了oprot.write,只不过这里有Struct,Field.目测这里用  schemes.get(oprot.getScheme()).getScheme().write(oprot, this);就是因为args的一些参数在静态初始化的时候已经放入了schemes

   public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}
   public void write(org.apache.thrift.protocol.TProtocol oprot, demoMethod_args struct) throws org.apache.thrift.TException {
struct.validate(); oprot.writeStructBegin(STRUCT_DESC);
if (struct.param1 != null) {
oprot.writeFieldBegin(PARAM1_FIELD_DESC);
oprot.writeString(struct.param1);
oprot.writeFieldEnd();
}
if (struct.param2 != null) {
oprot.writeFieldBegin(PARAM2_FIELD_DESC);
struct.param2.write(oprot);
oprot.writeFieldEnd();
}
if (struct.param3 != null) {
oprot.writeFieldBegin(PARAM3_FIELD_DESC);
{
oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, struct.param3.size()));
for (Map.Entry<String, String> _iter4 : struct.param3.entrySet())
{
oprot.writeString(_iter4.getKey());
oprot.writeString(_iter4.getValue());
}
oprot.writeMapEnd();
}
oprot.writeFieldEnd();
}
oprot.writeFieldStop();
oprot.writeStructEnd();
} }

到此为止,send_domoMethod完毕,接下来就是recv_demoMethod()也就是接受服务端返回的数据。

  public int recv_demoMethod() throws org.apache.thrift.TException
{
demoMethod_result result = new demoMethod_result();//与封装请求参数类似,加入一些内容到schema中
receiveBase(result, "demoMethod");//读取数据进行一些组装工作
if (result.isSetSuccess()) {
return result.success;//返回result中的success值
}
throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "demoMethod failed: unknown result");
}
  protected void receiveBase(TBase result, String methodName) throws TException {//读取返回结果,并将返回结果组装好放到result中
TMessage msg = iprot_.readMessageBegin();
if (msg.type == TMessageType.EXCEPTION) {
TApplicationException x = TApplicationException.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
if (msg.seqid != seqid_) {
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response");
}
result.read(iprot_);//将所读取的数据封装成需要类型返回
iprot_.readMessageEnd();//这一步其实什么也没做,到此为止result其实已经形成
}

由于写入的时候有写入信息的类型,序号之类的东西,故这里读取和写入保持一致,也要readMessageBegin,只不过这里使用的是iprot_,其实还是Tprotocol。Tprotocol iprot_ ----->Ttransport trans_ ----->InputStream inputstream

 public TMessage readMessageBegin() throws TException {
int size = readI32();
if (size < 0) {
int version = size & VERSION_MASK;
if (version != VERSION_1) {
throw new TProtocolException(TProtocolException.BAD_VERSION, "Bad version in readMessageBegin");
}
return new TMessage(readString(), (byte)(size & 0x000000ff), readI32());
} else {
if (strictRead_) {
throw new TProtocolException(TProtocolException.BAD_VERSION, "Missing version in readMessageBegin, old client?");
}
return new TMessage(readStringBody(size), readByte(), readI32());
}
}

其中result.read(iprot_)还是对应着写入时候的args.write,代码贴出来:

 private static class demoMethod_resultStandardScheme extends StandardScheme<demoMethod_result> {

       public void read(org.apache.thrift.protocol.TProtocol iprot, demoMethod_result struct) throws org.apache.thrift.TException {
org.apache.thrift.protocol.TField schemeField;
iprot.readStructBegin();
while (true)
{
schemeField = iprot.readFieldBegin();
if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
break;
}
switch (schemeField.id) {
case 0: // SUCCESS
if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
struct.success = iprot.readI32();//在这里读取返回结果,这些结果的结构都是早已经定义好的,因为我们这里的例子是int类型,故这里只需要读取readI32即可
struct.setSuccessIsSet(true);
} else {
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
break;
default:
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
}
iprot.readFieldEnd();
}
iprot.readStructEnd(); // check for required fields of primitive type, which can't be checked in the validate method
struct.validate();
} public void write(org.apache.thrift.protocol.TProtocol oprot, demoMethod_result struct) throws org.apache.thrift.TException {
struct.validate(); oprot.writeStructBegin(STRUCT_DESC);
oprot.writeFieldBegin(SUCCESS_FIELD_DESC);
oprot.writeI32(struct.success);
oprot.writeFieldEnd();
oprot.writeFieldStop();
oprot.writeStructEnd();
} }

综上,整个客户端发请求以及接受返回数据也就是先写后读的一个完整过程也就完毕。整体流程图我就用从网上找到的一个例子来看就好了,除了方法不一样,其他都是一样的道理。

本文为博主原创,未经许可禁止转载。谢谢。

Thrift源码解析--TBinaryProtocol的更多相关文章

  1. Thrift源码解析--transport

    这一层主要是用于实现网络通信,现在都是基于Tcp/Ip,而Tcp/Ip协议栈由socket来实现,换句话说就是现在网络通信服务底层大都是通过socket实现的,在thrift源码中,就是将socket ...

  2. Thrift源码学习二——Server层

    Thrift 提供了如图五种模式:TSimpleServer.TNonblockingServer.THsHaServer.TThreadPoolServer.TThreadSelectorServe ...

  3. 第四章 dubbo内核之aop源码解析

    ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class); final P ...

  4. Flink 源码解析 —— 深度解析 Flink 是如何管理好内存的?

    前言 如今,许多用于分析大型数据集的开源系统都是用 Java 或者是基于 JVM 的编程语言实现的.最着名的例子是 Apache Hadoop,还有较新的框架,如 Apache Spark.Apach ...

  5. thrift源码分析

    1 前言 学习thrift源码主要为了弄清楚几个问题 thrift客户端和服务端的通信流程是如何的 thrift的IDL中给属性加上编号的作用是什么 thrift中require.optional和默 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  9. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

随机推荐

  1. [Q]复制授权了文件但仍显示“未注册”问题(安装在非默认目录或目录包含中文)

    1. 注意要将解压后的文件复制到CAD批量打图精灵安装目录,而不要复制文件夹,复制是提示是否需要覆盖,要选择覆盖. 2. 若通过第1步操作仍然显示“未注册”,则可能是由于安装目录含有中文或者不是默认目 ...

  2. DEBUG 调试

    1.Step Into (also F5) 跳入 2.Step Over (also F6) 跳过 3.Step Return (also F7) 执行完当前method,然后return跳出此met ...

  3. LNA

    low noise amplifier ,低噪声放大器. PA主要侧重输出功率,LNA侧重噪声系数,所以LNA用于前级,PA用作末级.

  4. vector,list,deque

    stl提供了三个最基本的容器:vector,list,deque. vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由 ...

  5. hdu 1846 Brave Game 简单博弈

    Problem Description 十年前读大学的时候,中国每年都要从国外引进一些电影大片,其中有一部电影就叫<勇敢者的游戏>(英文名称:Zathura),一直到现在,我依然对于电影中 ...

  6. Mac最好的虚拟机软件Parallels,体验比Vmware棒

    每一位Mac电脑用户,必须安装虚拟机软件,在虚拟机里面安装Windows系统,解决日常必须用windows软件的问题,解决国内网银登录的兼容问题. 你一定不要用Mac系统自带的boot camp方式安 ...

  7. 面试题-Java Web-JSP部分

    1.什么是JSP页面? JSP页面是一种包含了静态数据和JSP元素两种类型的文本的文本文档.静态数据可以用任何基于文本的格式来表示,比如:HTML或者XML.JSP是一种混合了静态内容和动态产生的内容 ...

  8. centos7 搭建elk

    CentOS 7.x安装ELK(Elasticsearch+Logstash+Kibana)  云计算 Aug 162015 第一次听到ELK,是新浪的@ARGV 介绍内部使用ELK的情况和场景,当时 ...

  9. PHP ckeditor富文本编辑器 结合ckfinder实现图片上传功能

    一:前端页面代码 <script src="/www/res/ckeditor/ckeditor.js"></script> <textarea id ...

  10. linux系统被入侵后处理经历【转】

    背景 操作系统:Ubuntu12.04_x64 运行业务:公司业务系统,爬虫程序,数据队列. 服务器托管在外地机房. 突然,频繁收到一组服务器ping监控不可达邮件,赶紧登陆zabbix监控系统查看流 ...