Thrift源码分析(三)-- IDL和生成代码分析

IDL是很多RPC框架用来支持跨语言环境调用的一个服务描述组件,一般都是采用文本格式来定义。 更多IDL的思考查看《理解WSDL, IDL》

Thrift的不同版本定义IDL的语法也不太相同,这里使用Thrift-0.8.0这个版本来介绍Java下的IDL定义

1. namespace 定义包名

2. struct 定义服务接口的参数,返回值使用到的类结构。如果接口的参数都是基本类型,则不需要定义struct

3. service 定义接口

一个简单的例子,IDL文件以.thrift为后缀名。 

demo.thrift

1. 定义了生成的Java文件的包名为com.thrift.test

2. 定义了一个struct类结构作为参数

3.定义了一个service接口,返回值是int,方法名叫demoMethod,参数有三个,第一个是字符串类型,第二个是上面定义的类Parameter,第三个是Map类型

namespace java com.thrift.test

struct Parameter{
	1: required i32 id;
	2: required string name;
}

service DemoService{
	i32 demoMethod(1:string param1, 2:Parameter param2, 3:map<string,string> param3);
}

IDL支持的数据类型包括以下部分

bool 布尔型
byte 8位整数
i16  16位整数
i32  32位整数
i64  64位整数
double 双精度浮点数
string 字符串
binary 字节数组
list<i16> List集合,必须指明泛型
map<string, string> Map类型,必须指明泛型
set<i32> Set集合,必须指明泛型

有了IDL之后,就可以使用thrift来自动生成辅助代码,包括客户端代码和序列化接口的代码

thrift -r --gen java demo.thrift

生成的代码如下

每个Struct会单独生成一个类,每个Service会生成一个类。

看一下生成类的具体结构

生成的类主要有5个部分

1. 接口类型,默认名称都是Iface。这个接口类型被服务器和客户端共同使用。服务器端使用它来做顶层接口,编写实现类。客户端代码使用它作为生成代理的服务接口。

自动生成的接口有两个,一个是同步调用的Iface,一个是异步调用的AsyncIface。异步调用的接口多了一个回调参数。

public interface Iface {

    public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException;

  }

  public interface AsyncIface {

    public void demoMethod(String param1, Parameter param2, Map<String,String> param3, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.demoMethod_call> resultHandler) throws org.apache.thrift.TException;

  }

2. 客户端类型,一个同步调用的客户端Client,一个异步调用的客户端AsyncClient

3. Processor,用来支持方法调用,每个服务的实现类都要使用Processor来注册,这样最后服务器端调用接口实现时能定位到具体的实现类。后面会有专门的文章介绍

4.方法参数的封装类,以"方法名_args"命名

5.方法返回值的封装类,以"方法名_result"命名

看一下生成的同步调用客户端Client的具体代码

1. 提供一个工厂方法来创建Client对象

2.接口方法的客户端代理,只做了两件事,发送方法调用请求;接收返回值

发送方法调用请求做了2件事

1. 创建方法参数对象,封装方法参数

2. 调用父类的sendBase方法来发送消息。发送消息时先通过writeMessageBegin发送消息头,再调用方法参数对象的write(TProtocol)方法发送消息体,最后结束发送

接受调用返回值做了2件事

1. 创建方法返回值对象,封装方法返回值

2. 调用父类的receiveBase方法接收方法返回值。先通过receiveMessage接收消息体,处理异常,然后调用方法参数对象的read(TProtocol)方法来接收消息体,最后结束接收

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) {
        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 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");
    }

  }

//org.apache.thrift.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();
  }

  protected void receiveBase(TBase result, String methodName) throws TException {
    // 接收消息头
    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();
  }

看一下方法参数对象

方法参数实现了TBase接口,TBase接口定义了一个对象在某种协议下的编解码接口。

public interface TBase<T extends TBase<?,?>, F extends TFieldIdEnum> extends Comparable<T>,  Serializable {
  public void read(TProtocol iprot) throws TException;

  public void write(TProtocol oprot) throws TException;
}

方法参数对象主要做了2件事

1. 创建每个参数的元数据,包括参数类型,顺序号。顺序号是在IDL定义的时候设置的,用来识别参数的位置,在编解码的时候有用

2. 实现自己的编解码方法, read(TProtocol), write(TProtocol)。这里又把具体的编解码功能委托给了XXXScheme类

public static class demoMethod_args implements org.apache.thrift.TBase<demoMethod_args, demoMethod_args._Fields>, java.io.Serializable, Cloneable   {
    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("demoMethod_args");

    private static final org.apache.thrift.protocol.TField PARAM1_FIELD_DESC = new org.apache.thrift.protocol.TField("param1", org.apache.thrift.protocol.TType.STRING, (short)1);
    private static final org.apache.thrift.protocol.TField PARAM2_FIELD_DESC = new org.apache.thrift.protocol.TField("param2", org.apache.thrift.protocol.TType.STRUCT, (short)2);
    private static final org.apache.thrift.protocol.TField PARAM3_FIELD_DESC = new org.apache.thrift.protocol.TField("param3", org.apache.thrift.protocol.TType.MAP, (short)3);

    private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>();
    static {
      schemes.put(StandardScheme.class, new demoMethod_argsStandardSchemeFactory());
      schemes.put(TupleScheme.class, new demoMethod_argsTupleSchemeFactory());
    }

    public String param1; // required
    public Parameter param2; // required
    public Map<String,String> param3; // required

    /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
    public enum _Fields implements org.apache.thrift.TFieldIdEnum {
      PARAM1((short)1, "param1"),
      PARAM2((short)2, "param2"),
      PARAM3((short)3, "param3");

      private static final Map<String, _Fields> byName = new HashMap<String, _Fields>();

      static {
        for (_Fields field : EnumSet.allOf(_Fields.class)) {
          byName.put(field.getFieldName(), field);
        }
      }

  // 对象自己负责解码
  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
      schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
    }
<pre name="code" class="java">  // 对象自己负责编码

public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {

schemes.get(oprot.getScheme()).getScheme().write(oprot, this);

}


再来看XXXScheme类,这也是自动生成的,是方法参数XXX_args的内部类。

Scheme有两类,一个是StandardScheme,使用消息头+消息体的方式来编解码对象。一个是TupleScheme,直接采用写消息体的方式编解码,编码字节流更小。

拿demoMethod_argsStandardScheme举例,

1. 它的编码方法就是从writeStructBegin开始逐个写字段,每个字段写之前会writeFieldBegin开始,写字段类型和字段的顺序号。如果字段是一个类Struct,就调用这个类自己的编码方法write(TProtocol)。Thrift会给每个Struct生成类,这些类里面定义了这个类的编解码方法。最后写完之后以writeStructEnd结束

2. 它的解码方法从readStructBegin开始,然后读字段元数据readFieldBegin,读1个字节的字段类型,2个字段的字节顺序号,然后根据字段类型,来读相应类型长度的数据。直到读完用readStructEnd结束。

 private static class demoMethod_argsStandardScheme extends StandardScheme<demoMethod_args> {

      public void read(org.apache.thrift.protocol.TProtocol iprot, demoMethod_args 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 1: // PARAM1
              if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
                struct.param1 = iprot.readString();
                struct.setParam1IsSet(true);
              } else {
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;
            case 2: // PARAM2
              if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) {
                struct.param2 = new Parameter();
                struct.param2.read(iprot);
                struct.setParam2IsSet(true);
              } else {
                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
              }
              break;
            case 3: // PARAM3
              if (schemeField.type == org.apache.thrift.protocol.TType.MAP) {
                {
                  org.apache.thrift.protocol.TMap _map0 = iprot.readMapBegin();
                  struct.param3 = new HashMap<String,String>(2*_map0.size);
                  for (int _i1 = 0; _i1 < _map0.size; ++_i1)
                  {
                    String _key2; // required
                    String _val3; // required
                    _key2 = iprot.readString();
                    _val3 = iprot.readString();
                    struct.param3.put(_key2, _val3);
                  }
                  iprot.readMapEnd();
                }
                struct.setParam3IsSet(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_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();
      }

    }
 private static class demoMethod_argsTupleScheme extends TupleScheme<demoMethod_args> {

      @Override
      public void write(org.apache.thrift.protocol.TProtocol prot, demoMethod_args struct) throws org.apache.thrift.TException {
        TTupleProtocol oprot = (TTupleProtocol) prot;
        BitSet optionals = new BitSet();
        if (struct.isSetParam1()) {
          optionals.set(0);
        }
        if (struct.isSetParam2()) {
          optionals.set(1);
        }
        if (struct.isSetParam3()) {
          optionals.set(2);
        }
        oprot.writeBitSet(optionals, 3);
        if (struct.isSetParam1()) {
          oprot.writeString(struct.param1);
        }
        if (struct.isSetParam2()) {
          struct.param2.write(oprot);
        }
        if (struct.isSetParam3()) {
          {
            oprot.writeI32(struct.param3.size());
            for (Map.Entry<String, String> _iter5 : struct.param3.entrySet())
            {
              oprot.writeString(_iter5.getKey());
              oprot.writeString(_iter5.getValue());
            }
          }
        }
      }

      @Override
      public void read(org.apache.thrift.protocol.TProtocol prot, demoMethod_args struct) throws org.apache.thrift.TException {
        TTupleProtocol iprot = (TTupleProtocol) prot;
        BitSet incoming = iprot.readBitSet(3);
        if (incoming.get(0)) {
          struct.param1 = iprot.readString();
          struct.setParam1IsSet(true);
        }
        if (incoming.get(1)) {
          struct.param2 = new Parameter();
          struct.param2.read(iprot);
          struct.setParam2IsSet(true);
        }
        if (incoming.get(2)) {
          {
            org.apache.thrift.protocol.TMap _map6 = new org.apache.thrift.protocol.TMap(org.apache.thrift.protocol.TType.STRING, org.apache.thrift.protocol.TType.STRING, iprot.readI32());
            struct.param3 = new HashMap<String,String>(2*_map6.size);
            for (int _i7 = 0; _i7 < _map6.size; ++_i7)
            {
              String _key8; // required
              String _val9; // required
              _key8 = iprot.readString();
              _val9 = iprot.readString();
              struct.param3.put(_key8, _val9);
            }
          }
          struct.setParam3IsSet(true);
        }
      }
    }

  }

方法返回值封装类的结构和方法参数封装类的结构和原理完全一致,这里不再赘述。

  

时间: 2024-10-11 08:08:01

Thrift源码分析(三)-- IDL和生成代码分析的相关文章

IDL和生成代码分析

IDL:接口描述语言 这里使用thrift-0.8.0-xsb这个版本来介绍IDL的定义以及简单实例分析. 1. namespace 定义包名 2.struct 结构体,定义服务接口的参数和返回值用到的类结构.基本类型不需要使用struct. 3.service 定义接口:demo.thrift 1 namespace java com.thrift.demo 2 3 struct Parameter { 4 1: required i32 id; 5 2: required string na

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"

Java集合源码学习笔记(二)ArrayList分析

Java集合源码学习笔记(二)ArrayList分析 >>关于ArrayList ArrayList直接继承AbstractList,实现了List. RandomAccess.Cloneable.Serializable接口,为什么叫"ArrayList",因为ArrayList内部是用一个数组存储元素值,相当于一个可变大小的数组,也就是动态数组. (1)继承和实现继承了AbstractList,实现了List:ArrayList是一个数组队列,提供了相关的添加.删除.修

Apache Spark源码走读之7 -- Standalone部署方式分析

欢迎转载,转载请注明出处,徽沪一郎. 楔子 在Spark源码走读系列之2中曾经提到Spark能以Standalone的方式来运行cluster,但没有对Application的提交与具体运行流程做详细的分析,本文就这些问题做一个比较详细的分析,并且对在standalone模式下如何实现HA进行讲解. 没有HA的Standalone运行模式 先从比较简单的说起,所谓的没有ha是指master节点没有ha. 组成cluster的两大元素即Master和Worker.slave worker可以有1到

执行LS源码的三种方法

方法一:使用 eval() eval()函数常用来计算表达式,将表达式转换成一个变量名或者对象名,然后使用它访问变量或者对象. LS中的数据都是以字符串的形式存储.当获取到字符串 data时(json数据转换成的字符串), storage = eval("("+data+")");//这样得到的数据就是json串. 备注:在json转换成本地存储的时候,需要将json串转换一下:JSON.stringify(data),这样得到的就是json串.. 方法二:使用ne

Thrift源码分析(一)-- 基本概念

我所在的公司使用Thrift作为基础通信组件,相当一部分的RPC服务基于Thrift框架.公司的日UV在千万级别,Thrift很好地支持了高并发访问,并且Thrift相对简单地编程模型也提高了服务地开发效率. Thrift源于Facebook, 目前已经作为开源项目提交给了Apahce.Thrift解决了Facebook各系统的大数据量传输通信和内部不同语言环境的跨平台调用. Thrift的官方网站: http://thrift.apache.org/ 作为一个高性能的RPC框架,Thrift的

Thrift源码解析--TBinaryProtocol

本文为原创,未经许可禁止转载. 关于Tprotocol层都是一些通信协议,个人感觉内容较大,很难分类描述清楚.故打算以TBinaryProtocol为例,分析客户端发请求以及接收服务端返回数据的整个过程. 先将客户端的测试用例贴上. 1 public class DemoClient { 2 public static void main(String[] args) throws Exception{ 3 String param1 = "haha"; 4 Map<String

Thrift 源码学习一

Thrift 客户端与服务端的交互图 源码结构 传输层 TTransport: TTransport:客户端传输层抽象基础类,read.write.flush.close 等方法 TSocket 与 TNonBlockingSocket:分别是基于 BIO 和 NIO 客户端传输类 TServerSocket 与 TNonBlockingServerSocket:分别是基于 BIO 和 NIO 服务端传输类 TZlibTransport: TSaslClientTransport 与 TSasl

Django Rest Framework源码剖析(三)-----频率控制

一.简介 承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡.提高服务器配置.通过代理限制访问频率等,但是django rest framework自身就提供了访问频率的控制,可以从代码本身做控制. 二.频率控制内部原理概述 django rest framework 中频率控制基本原理基于访问次数和时间,通过计算实现,当然我们也可以自己定义频率控制方法.基本原理如下: 启用频率