Thrift源码解析--TBinaryProtocol

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

关于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.

Thrift源码解析--TBinaryProtocol

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:

Thrift源码解析--TBinaryProtocol

接下来就是在已有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目前状态如下图:

Thrift源码解析--TBinaryProtocol

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的状态如下图:

Thrift源码解析--TBinaryProtocol

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的状态:

Thrift源码解析--TBinaryProtocol

Thrift源码解析--TBinaryProtocol

接下来就是:

 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

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

上一篇:Bash中使用MySQL导入导出CSV格式数据[转]


下一篇:Visual Studio Code搭建python开发环境