带你搞懂Thrift核心源码(JAVA)

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


注:本篇文章适合使用过thrift,但不识庐山真面目的人阅读
本篇文章主要目的是了解thrift的全貌。读完文章你将收获以下几点

  • Thrift到底是个什么东西?
  • Thrift在项目中充当的角色?
  • Thrift的架构设计?
  • Thrift的TTransport是什么?(重点)
  • Thrift的TProtocol是什么?(重点)
  • Thrift的TProcessor是什么?(重点)
  • Thrift的scheme是什么?
  • Thrift中使用了哪几种Socket I/O模型?




    旁白:一天,公司需要将某个老系统,从单体架构重构成微服务架构,任命小黑(大牛)为总负责人,小黄(老鸟)和小白(刚入职的应届小菜)打下手,为此,小黑开始带着小黄和小白进行技术选型……
    小黑:好了,现在到RPC框架的选型了,到底用哪个比较好呢?你们说说看。
    小黄:要我说啊,可以试试阿里的dubbo,非常适合java技术栈呢听说。
    小白:……这个,弱弱问一下,RPC框架是啥?
    小黄:(一脸鄙视)这都不懂,不就是RPC嘛
    小白:(就不能多点包容心,带带新手)我去搜了一下,RPC是远程过程调用,是不是也是一种网络通信?
    小黄:(嘚瑟的样子)就是可以远程调用别的服务的接口,类似于调用本地接口一样简单方便。
    小黑:(得给新来的同学一点鼓励)小白你说的没错,说到底也就是一种网络通信,只不过在其上层加入了自定义的通信协议,其目的就是为了按照一定的规则,实现远程接口调用。
    小白:嗯……好像有点头绪了。
    小黑:不急,等我们选定了框架,我会抽时间做个专题,给你们从源码层次去了解RPC框架。
    小黄:所以我们是要选dubbo了吗?
    小黑:你想想,咱们公司用的是什么RPC框架?你如果要用dubbo,稳定性谁来保证呢?出了问题是不是还得自己运维?增加了多少成本?
    小黄:(那你还叫我们讨论……)嗯,没错,所以要用thrift是吧?
    小黑:嗯,不用重复造轮子,有现成的基建,为何不用。但是用之前,一定要知根知底,不仅要知其然,然还知其所以然,防止以后bug多多。
    小白:好耶,双手赞成,我先回去做足功课,这个小意思!
    小黄:我正好也打算再深入了解一下。
    小黑:那好,下周二,我给你们讲讲。


    周二……


    小黑:小白,对thrift学得怎么样了?
    小白:嘿嘿,已经会用了,也debug进去看过源码了,虽然没太看懂。。。
    小黑:嗯,已经不错了,待会讲的时候,你应该能听懂了。
    小黄:小黑你就别墨迹了,赶紧讲吧。
    小黑:好好好,我们开始,从头开始来哈,小黄你就忍耐点,让小白跟得上,毕竟你已经是老司机了。
    小黄:好吧,小白你可要认真听了。
    小白:收到!
    小黑:讲之前,给你们看看百度百科的定义和官网的定义

Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。 – 百度百科

Apache Thrift is a software project spanning a variety of programming languages and use cases. Our goal is to make reliable, performant communication and data serialization across languages as efficient and seamless as possible. Originally developed at Facebook, Thrift was open sourced in April 2007 and entered the Apache Incubator in May, 2008. Thrift became an Apache TLP in October, 2010. – Thrift官网

小白:跨语言?意思是不同语言之间也能通过它进行调用吗?
小黑:嗯,是的,比如java提供接口,python调用,那是可行的,这个呢不再重点讨论范围内哈。重点是要知道它是一个远程过程调用,大白话讲就是,A服务通过网络通信的形式远程调用B服务的接口,这就是RPC调用。
小白:嗯嗯,这个了解,和之前想的一样。
小黑:所以你也应该知道它在我们的项目中充当什么角色吧?
小白:嗯,相当于将服务解耦,通过RPC调用达到这个目的。
小黑:没错,这也正是微服务的精髓所在,通过RPC和消息进行服务之间的解耦,这样一个大的系统就可以被划分为细小的服务,从而提高可用性、易扩张性。
小白:那这样就会增加运维的成本了吧?
小黑:有失必有得,这个失呢在可承受范围内时,就可以接受了。
小白:嘿嘿,明白。
小黑:接下来,我们来弄清楚thrift的架构是如何设计的,其实就是代码中的各个模块是如何设计的。我们先来看一张图,最简单的网络通信。

在这里插入图片描述

小黄:(太简单了,玩会手机)……
小白:嗯,这就是简单的发送和接受消息
小黑:没错,我们再来看下一张图,这次加了一个协议在两者之间
在这里插入图片描述
小白:这样做的目的是啥?直接发送不就好了吗?
小黑:那我问你,如果我是持续的流发送,不是类似这种发一串字符停一下,对方再发一串回来,你如何正确的解析出Hi和Hello这两个词?例如字符流hihihihihihihihihihihihihellohellohellohellohihihihihi,你是一次读取多少个呢?
小白:只需要对方告诉这次发送的单词的长度,那我就可以从流中读取这个单词出来。
小黑:是吗?再想想。
小白:哦~,还需要知道开始读取的位置,然后依次从这个流中读取出来。
小黑:确实是这样,你们不要被网上那些入门的例子给误导,形成了Socket通信的时候是基于字符串的形式,你发一串我接收一串。其实只要是基于TCP通信的,都是流的方式,就像水一样,源源不断,你需要一个桶或者其它容器,去一次次的盛满它。
小白:我明白了,所以需要一种发送接收协议,将每一份数据封装成一个数据帧,以这个为单位进行编解码,是吗?
小黑:嗯,说到点上了,就像上图一样。接下来,我们看看下一张图
在这里插入图片描述
小黑:是不是和上一张图有点像?只是从发送简单的信息到发送接口调用的信息而已。
小白:看出来了,这个是不是就相当于每一次接口调用,都封装成一个数据帧,发送到Server,然后Server根据协议进行解码,获取到Client想要调用的接口以及接口参数,之后进行本地接口调用,再将结果通过一个数据帧返回给Client。
小黑:哈哈,完成正确,孺子可教也。到这里,Thrift的精髓你以为知道了,所以是不是没什么难的?
小白:嗯嗯,也不过如此嘛
小黑:好了,别骄傲了,还有很多没讲呢,这不过是让你知道了它的一些本质而已,实际编码起来还是比较复杂的。
小白:好!小黑哥继续!
小黄:(抬头一看,才讲到这)小黑可要快点了,不然今天就不用干活啦。
小黑:不急不急,今天讲不完,可要明天讲,不能操之过急,不然学不到位。
小黄:好吧,你是大佬你说的算。
小黑:那我们继续,接下来我们再看一张图
在这里插入图片描述
小黑:小白这个图看得懂吗?
小白:嗯,这个源码里面我也看到了有TTransport这个类,我还一直搞不明白它和TProtocol的区别,现在看到你这张图,我瞬间明白了。TTransport相当于是用什么Socket模型来进行网络通信,常见的有BIO、NIO和AIO,而TProtcol则是相当于将数据根据给定的协议编码后发送,然后根据给定的协议解码接收到的数据。
小黑:聪明,其实就是这么回事。那我们再来看下一张图
在这里插入图片描述
小黑:这个图呢?能看懂吗?
小白:呃。。。说实话,我搞不明白为啥要有TProcessor这个类是干啥用的,不是可以根据解码到的信息直接调用方法吗?
小黑:这个类确实比较费解,但是如果从源码层面上理解,就简单多了,后面我们再说这个。现在先猜猜,这个类的作用是干嘛的,从名字上讲,处理器,说明它是处理解码到的接口调用信息,然后去实际调用本地实现,这个可以理解吧?
小白:可以理解,就是对这个类还是有点朦胧。
小黑:没关系,先从整体架构上了解Thrift的模块都有什么用,如何协同工作的就行了。
小白:嗯,这个了解了。
小黑:那你总结一下?
小白:好,简单来说,Thrift有三个重要的模块,分别是TTransport、TProtocol和TProcessor,TTransport是用来真正通信的模块,TProtocol是用来定义协议和编解码信息,TProcessor是用来将解码后的接口调用信息进行真正的本地调用,并且只有接口提供方需要这个模块。
小黑:嗯,很好,总结得不错,看来这一个星期确实有下功夫。
小黄:(哼,这么简单,继续玩手机)
小黑:好了,接下来要开始将源码了,小黄你就别玩手机了,不然待会你也听不懂。
小黄:(笑话,这么简单)嗯,好的(偶尔还玩玩手机的样子……)
小黑:(待会有你不懂的时候……)首先,Thrift的源码说多不多,说少不少,但是重点的确实不多,所以我们挑重点的源码讲,其它无关的模块和源码就不掺和进来了。我们以最简单的TSimpleServer为例开始讲。小白,你看过TSimpleServer的源码吗?
小白:看过,但是没怎么看明白……
小黑:你是不是没看过java BIO、NIO和AIO?
小白:确实没怎么研究……
小黑:那你可以先去看看深入浅出JAVA BIO、NIO和AIO(附详细代码实例)这篇文章,看完之后再回过头来看看Thrift源码,你会觉得原来这么简单。
小白:真的吗?哇,你看今天也不早了,要不源码明天再开始讲?让我们回去消化消化~
小黄:(纳尼?今天就讲这些……)那就照顾照顾一下小白,明天再继续吧
小黑:嗯,我正有此意,一次性讲太多,不好接受啊,哈哈,那就明天继续拉~


第二天……


小黑:好了好了,我们继续,小白你昨天看得怎么样了,对BIO、NIO和AIO了解得怎么样了
小白:嘿嘿,除了AIO,其它两个我都已经熟悉了!
小黑:正好Thrift不涉及AIO,熟悉其它两个就已经够了,那我们继续,昨天讲到从TSimpleServer开始,先来看服务器代码。(注:demo来自从零开发一个后端服务(三)–thrift 服务骨架编写这篇文章)

public static void start() throws TTransportException {
		//创建一个服务端socket,最简单的,只需要指定绑定的端口
		TServerSocket tServerSocket = new TServerSocket(9000);
		//暂且理解为使用thrift的默认传输协议,我也没具体研究
		TProtocolFactory protocolFactory = new TBinaryProtocol.Factory();
		//创建一个处理器,看代码最后new TCalculateImpl()就知道,thrift需要知道,是哪个类来处理接口的请求。
		TProcessor tProcessor = new TCalculate.Processor<TCalculate.Iface>(new TCalculateImpl());

		//将TServerSocket、protocol和processor作为参数传进去
		TSimpleServer.Args tArgs = new TSimpleServer.Args(tServerSocket);
		tArgs.processor(tProcessor);
		tArgs.protocolFactory(protocolFactory);

		// 创建 TSimpleServer
		TSimpleServer server = new TSimpleServer(tArgs);

		// 启动服务器
		System.out.println("[info] min system bootup successful");
		server.serve();
}

小白:这代码第一次自己看的时候,云里雾里的,不知道那些参数为啥要这样设置。但是经过你之前对Transport、Protocol和Processor的分析,现在看就很明了了。
小黑:不错,其实就是设置了这三个必要的参数而已,然后传进去,启动服务。所以这段代码没啥看点,我们继续往下看TSimpleServer源码。

public class TSimpleServer extends TServer {

  	public TSimpleServer(AbstractServerArgs args) {
    	super(args);
	}
	……
}

小黑:可以看到,参数又传给了父类

protected TServer(AbstractServerArgs args) {
    processorFactory_ = args.processorFactory;
    serverTransport_ = args.serverTransport;
    inputTransportFactory_ = args.inputTransportFactory;
    outputTransportFactory_ = args.outputTransportFactory;
    inputProtocolFactory_ = args.inputProtocolFactory;
    outputProtocolFactory_ = args.outputProtocolFactory;
  }

……

public T transportFactory(TTransportFactory factory) {
      this.inputTransportFactory = factory;
      this.outputTransportFactory = factory;
      return (T) this;
}
public T protocolFactory(TProtocolFactory factory) {
      this.inputProtocolFactory = factory;
      this.outputProtocolFactory = factory;
      return (T) this;
}

小白:一直想问,为什么要用Factory?还有,为啥input和output都用的同一个factory?
小黑:不急,继续往下看,待会你就知道了,接下来看看服务启动的代码。

public void serve() {
    try {
      //设置accept等待时间
      serverTransport_.listen();
    } catch (TTransportException ttx) {
      LOGGER.error("Error occurred during listening.", ttx);
      return;
    }

    //事件处理器,扩展用
    if (eventHandler_ != null) {
      eventHandler_.preServe();
    }

    setServing(true);

    //stopped_是一个volatile的,说明可以从任意线程停止服务器
    while (!stopped_) {
      TTransport client = null;
      TProcessor processor = null;
      TTransport inputTransport = null;
      TTransport outputTransport = null;
      TProtocol inputProtocol = null;
      TProtocol outputProtocol = null;
      ServerContext connectionContext = null;
      try {
        //客户端Socket
        client = serverTransport_.accept();
        if (client != null) {
          //获取服务端接口处理器,这个工厂什么也没做,直接返回了processor
          processor = processorFactory_.getProcessor(client);
          //获取输入输出Transport,可以利用工厂进行包装,但是当前没做任何操作直接返回
          inputTransport = inputTransportFactory_.getTransport(client);
          outputTransport = outputTransportFactory_.getTransport(client);
          //获取输入输出Protocol,两个都有相同的Transport,只是new了两个Protocol
          inputProtocol = inputProtocolFactory_.getProtocol(inputTransport);
          outputProtocol = outputProtocolFactory_.getProtocol(outputTransport);
          //事件处理器,扩展用
          if (eventHandler_ != null) {
            connectionContext = eventHandler_.createContext(inputProtocol, outputProtocol);
          }
          while (true) {
            //事件处理器,扩展用
            if (eventHandler_ != null) {
              eventHandler_.processContext(connectionContext, inputTransport, outputTransport);
            }
            //真正处理请求的地方,包括通信、编解码、接口调用
            if(!processor.process(inputProtocol, outputProtocol)) {
              break;
            }
          }
        }
      } catch (TTransportException ttx) {
        // Client died, just move on
      } catch (TException tx) {
        if (!stopped_) {
          LOGGER.error("Thrift error occurred during processing of message.", tx);
        }
      } catch (Exception x) {
        if (!stopped_) {
          LOGGER.error("Error occurred during processing of message.", x);
        }
      }

      if (eventHandler_ != null) {
        eventHandler_.deleteContext(connectionContext, inputProtocol, outputProtocol);
      }

      if (inputTransport != null) {
        inputTransport.close();
      }

      if (outputTransport != null) {
        outputTransport.close();
      }

    }
    setServing(false);
  }

小白:咦,我看到了,原来在这里用到了,我还找到源码了

public class TProcessorFactory {
  private final TProcessor processor_;
  public TProcessorFactory(TProcessor processor) {
    processor_ = processor;
  }
  //直接返回了,做任何事
  public TProcessor getProcessor(TTransport trans) {
    return processor_;
  }
}
……
//TTransport工程,看注释就明白,可以对TTransport进行包装后再返回,易于扩展
public class TTransportFactory {

  /**
   * Return a wrapped instance of the base Transport.
   *
   * @param trans The base transport
   * @return Wrapped Transport
   */
  public TTransport getTransport(TTransport trans) {
    return trans;
  }
}
……
//这是一个接口类
public interface TProtocolFactory extends Serializable {
  public TProtocol getProtocol(TTransport trans);
}
……
//这是在TBinaryProtocol中的Factory静态类里面的实现的
public TProtocol getProtocol(TTransport trans) {
      return new TBinaryProtocol(trans, stringLengthLimit_, containerLengthLimit_, strictRead_, strictWrite_);
}

小黑:不错,其实做了这么多,也还是透传那三个必要的参数而已,说了这么久,来一张图给你们看看,理解得更透彻一点。
在这里插入图片描述
小白:看这个图的重点是不是在process这个方法里?
小黑:没错,前面只是将Socket和编解码协议准备好,接下来呢,就开始按照给定的协议进行数据的接收与编解码。
小白:嘿嘿,这个很容易懂了,我自己画了个简单的int类型数据发送&接收的例子,你看看对不对。这是Thrift的二进制通信协议,这是它们的写和读方法。
在这里插入图片描述
小白:然后,假设发送一个整型123456789,相当于将一个4字节的整型拆成一个个字节然后一次性发送过去。
在这里插入图片描述
小白:然后,按照相同的规则,将接收到的这四个单一的字节组合成一个四字节的整型
在这里插入图片描述
小黑:嗯,不错,你说的这个是process方法最里面的实现了。直接到了网络通信那一步了。既然你提了这个,那我顺便把代码打开给你看看。

public void writeI32(int i32) throws TException {
    inoutTemp[0] = (byte)(0xff & (i32 >> 24));
    inoutTemp[1] = (byte)(0xff & (i32 >> 16));
    inoutTemp[2] = (byte)(0xff & (i32 >> 8));
    inoutTemp[3] = (byte)(0xff & (i32));
    trans_.write(inoutTemp, 0, 4);
}
……
public int readI32() throws TException {
    byte[] buf = inoutTemp;
    int off = 0;

    if (trans_.getBytesRemainingInBuffer() >= 4) {
      buf = trans_.getBuffer();
      off = trans_.getBufferPosition();
      trans_.consumeBuffer(4);
    } else {
      readAll(inoutTemp, 0, 4);
    }
    return
      ((buf[off] & 0xff) << 24) |
      ((buf[off+1] & 0xff) << 16) |
      ((buf[off+2] & 0xff) <<  8) |
      ((buf[off+3] & 0xff));
}

小白:哈哈,看样子确实是这样。
小黑:嗯,其它类型的发送接收和这个是类似的原理,所以我后面就不再重复这个细节了。
小白:没问题,O了
小黑:那我们接着回到process这个方法,直接看代码

public abstract class TBaseProcessor<I> implements TProcessor {
  private final I iface;
  private final Map<String,ProcessFunction<I, ? extends TBase>> processMap;

  protected TBaseProcessor(I iface, Map<String, ProcessFunction<I, ? extends TBase>> processFunctionMap) {
    this.iface = iface;
    this.processMap = processFunctionMap;
  }

  public Map<String,ProcessFunction<I, ? extends TBase>> getProcessMapView() {
    return Collections.unmodifiableMap(processMap);
  }

  @Override
  public boolean process(TProtocol in, TProtocol out) throws TException {
    //开始读取,这第一步会读取到接口的名字
    TMessage msg = in.readMessageBegin();
    //根据接口的名字找实现
    ProcessFunction fn = processMap.get(msg.name);
    //如果该接口不存在,那么读结束,并且将异常信息发送给客户端,让其抛异常
    if (fn == null) {
      TProtocolUtil.skip(in, TType.STRUCT);
      in.readMessageEnd();
      TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'");
      out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));
      x.write(out);
      out.writeMessageEnd();
      out.getTransport().flush();
      return true;
    }
    //这个process方法里面是真正处理接口请求调用的
    fn.process(msg.seqid, in, out, iface);
    return true;
  }
}

小黑:我先问你个问题,你知道thrift为什么不支持方法重载吗?
小白:哈哈,我看到了processMap.get(msg.name),是不是因为这个是根据接口名字去找接口实现的,所以不支持重载。
小黑:没错,就是这样的。这个方法没啥好讲的,一眼就能看出它在说啥,重点呢还是在fn.process这个方法调用上,讲这个之前,先来看一张图。
在这里插入图片描述
小白:这个就是整个发送接收的流程是吗?
小黑:是的,写开始到结束,读开始到结束,这些方法都是一一匹配的。不知道你有没有发现,很多方法的实现都是空,你知道这么做的用意是什么吗?
小白:这不会是闲着蛋疼吧……
小黑:废话,当然不是拉,小黄,看你也不说说话,发表发表意见,这个你来说说看?
小黄:这个嘛,就是为了扩展,比如啊,你想在写XX之前或者之后做什么事,是不是直接实现这些接口就完事了?
小白:对啊!厉害厉害!
小黑:没错,这也是我们学习的榜样,如果有一天要写一个开源的东西,你就必须要考虑这些可扩展性的点,不能说写了一个框架,别人都不能自己扩展一些自己的东西,需要扩展,都要修改源码?那还得了,被别人喷到死啦~
小白:原来是这样~,我明白了!
小黑:嗯,孺子可教也。接下来看源码吧

public abstract class ProcessFunction<I, T extends TBase> {
  ……
  public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException {
    //这个就是获取参数的实例,比如这次请求的接口是calculate(Expression exp)
    //那么就会返回Expression的包装类
    T args = getEmptyArgsInstance();
    try {
      //这个包装类里,有自己的write/read方法实现
      args.read(iprot);
    } catch (TProtocolException e) {
      iprot.readMessageEnd();
      TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());
      oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
      x.write(oprot);
      oprot.writeMessageEnd();
      oprot.getTransport().flush();
      return;
    }
    //到这里接口调用参数就接收并解码完毕了,相当于将Expression获取到了
    iprot.readMessageEnd();
    TSerializable result = null;
    byte msgType = TMessageType.REPLY;

    try {
      //这里就相当于是调用本地实现,获取结果
      result = getResult(iface, args);
    } catch (TTransportException ex) {
      LOGGER.error("Transport error while processing " + getMethodName(), ex);
      throw ex;
    } catch (TApplicationException ex) {
      LOGGER.error("Internal application error processing " + getMethodName(), ex);
      result = ex;
      msgType = TMessageType.EXCEPTION;
    } catch (Exception ex) {
      LOGGER.error("Internal error processing " + getMethodName(), ex);
      if(!isOneway()) {
        result = new TApplicationException(TApplicationException.INTERNAL_ERROR,
            "Internal error processing " + getMethodName());
        msgType = TMessageType.EXCEPTION;
      }
    }
    //OneWay的意思是不需要回应Client的请求
    //所以!isOneway() 就是回应了呗
    if(!isOneway()) {
      oprot.writeMessageBegin(new TMessage(getMethodName(), msgType, seqid));
      //将结果发送给Client,result也有自己的read和write实现
      //例如这里的result就是ExpressionResult
      result.write(oprot);
      oprot.writeMessageEnd();
      oprot.getTransport().flush();
    }
  }

	……
  public abstract T getEmptyArgsInstance();
  public abstract TBase getResult(I iface, T args) throws TException;
  	……
}

小黑:经过前面的那些学习,再配合我写的这些注释,应该不难理解了吧?
小白:嘿嘿,可以说非常清晰了,如果再配合一个图,那就更好了,哈哈。
小黑:都这么清楚的流程了,图我就懒得画了,你要是有兴趣,自个回去画去。
小白:好吧好吧,其实我就是想看看图,这样不无聊,哈哈。
小黑:以后天天让你画图!好了,我们接着,上面那个方法中有两个比较重要的地方,你知道在哪不?
小白:肯定在你注释的那里,我猜第一个是args.read(iprot)吧,第二个肯定就是result = getResult(iface, args)了。
小黑:不错啊,看来你真的理解了嘛。那你猜猜,这两个方法里面都做了哪些事。
小白:这个简单,还记得之前讲fn.process之前,你画了张发送&接收的图示,里面有readStructBegin、readFieldBegin、readMapBegin和readBool等方法,但是在这个方法里看不到,所以我觉得,肯定在args.read(iprot)里面做了。
小黑:嗯,你猜得不错,readStructBegin的意思,就是开始读结构体嘛,就是在Dto里面定义的那些struct结构体,然后readFieldBegin就是开始读结构体里面的字段,readMapBegin等方法呢,就是可能这个字段的类型是Map,相当于复合类型,不是基础类型了,所以需要Begin和End配对,最后readBool等方法读的就是基础类型的字段,当然没有Begin和End这种配对了。
小白:你这么说,我感觉都不用点进去看了,都知道里面干啥了,哈哈
小黑:那细节还是要了解一下的嘛,我带你看看源码。

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

  private static final org.apache.thrift.protocol.TField OP_FIELD_DESC = new org.apache.thrift.protocol.TField("op", org.apache.thrift.protocol.TType.I32, (short)1);
  private static final org.apache.thrift.protocol.TField NUM1_FIELD_DESC = new org.apache.thrift.protocol.TField("num1", org.apache.thrift.protocol.TType.DOUBLE, (short)2);
  private static final org.apache.thrift.protocol.TField NUM2_FIELD_DESC = new org.apache.thrift.protocol.TField("num2", org.apache.thrift.protocol.TType.DOUBLE, (short)3);
  private static final org.apache.thrift.protocol.TField LI_FIELD_DESC = new org.apache.thrift.protocol.TField("li", org.apache.thrift.protocol.TType.LIST, (short)4);

  private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new ExpressionStandardSchemeFactory();
  private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new ExpressionTupleSchemeFactory();

  //这些字段是根据Dto里面的定义自动生成的
  public com.acme.service.Enums.OPERATE op; // required
  public double num1; // required
  public double num2; // required
  public java.util.List<java.lang.String> li; // optional
  
  //args.read(iprot),read的实现就是这个
  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
    //为什么要这样?不直接将read实现在这里?
    scheme(iprot).read(iprot, this);
  }

  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
    scheme(oprot).write(oprot, this);
  }  

  //可以理解为,这个工程类的作用就是返回这个ExpressionStandardScheme类
  private static class ExpressionStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
    public ExpressionStandardScheme getScheme() {
      return new ExpressionStandardScheme();
    }
  }

  //这就是Expression的read/write具体实现了
  private static class ExpressionStandardScheme extends org.apache.thrift.scheme.StandardScheme<Expression> {

    public void read(org.apache.thrift.protocol.TProtocol iprot, Expression 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;
        }
        //这就是为什么thrift的字段都需要标号,因为读写都是根据标号来的,一一对应 
        switch (schemeField.id) {
          case 1: // OP
            if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
              struct.op = com.acme.service.Enums.OPERATE.findByValue(iprot.readI32());
              struct.setOpIsSet(true);
            } else { 
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 2: // NUM1
            if (schemeField.type == org.apache.thrift.protocol.TType.DOUBLE) {
              struct.num1 = iprot.readDouble();
              struct.setNum1IsSet(true);
            } else { 
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 3: // NUM2
            if (schemeField.type == org.apache.thrift.protocol.TType.DOUBLE) {
              struct.num2 = iprot.readDouble();
              struct.setNum2IsSet(true);
            } else { 
              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
            }
            break;
          case 4: // LI
            if (schemeField.type == org.apache.thrift.protocol.TType.LIST) {
              {
                //符合类型为什么要有Begin和End就是因为需要读多个基础类型
                org.apache.thrift.protocol.TList _list0 = iprot.readListBegin();
                struct.li = new java.util.ArrayList<java.lang.String>(_list0.size);
                java.lang.String _elem1;
                for (int _i2 = 0; _i2 < _list0.size; ++_i2)
                {
                  _elem1 = iprot.readString();
                  struct.li.add(_elem1);
                }
                iprot.readListEnd();
              }
              struct.setLiIsSet(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
      //校验那些必须字段是否已经读取完毕,optional(可选)字段可以不需要
      struct.validate();
    }

    public void write(org.apache.thrift.protocol.TProtocol oprot, Expression struct) throws org.apache.thrift.TException {
      struct.validate();

      oprot.writeStructBegin(STRUCT_DESC);
      if (struct.op != null) {
        oprot.writeFieldBegin(OP_FIELD_DESC);
        oprot.writeI32(struct.op.getValue());
        oprot.writeFieldEnd();
      }
      oprot.writeFieldBegin(NUM1_FIELD_DESC);
      oprot.writeDouble(struct.num1);
      oprot.writeFieldEnd();
      oprot.writeFieldBegin(NUM2_FIELD_DESC);
      oprot.writeDouble(struct.num2);
      oprot.writeFieldEnd();
      if (struct.li != null) {
        if (struct.isSetLi()) {
          oprot.writeFieldBegin(LI_FIELD_DESC);
          {
            oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, struct.li.size()));
            for (java.lang.String _iter3 : struct.li)
            {
              oprot.writeString(_iter3);
            }
            oprot.writeListEnd();
          }
          oprot.writeFieldEnd();
        }
      }
      oprot.writeFieldStop();
      oprot.writeStructEnd();
    }

  }
  
  //这个还不太了解,没用到 
  private static class ExpressionTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
    public ExpressionTupleScheme getScheme() {
      return new ExpressionTupleScheme();
    }
  }

  private static class ExpressionTupleScheme extends org.apache.thrift.scheme.TupleScheme<Expression> {

    @Override
    public void write(org.apache.thrift.protocol.TProtocol prot, Expression struct) throws org.apache.thrift.TException {
      ……
    }

    @Override
    public void read(org.apache.thrift.protocol.TProtocol prot, Expression struct) throws org.apache.thrift.TException {
      ……
    }
  }

  private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) {
    return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme();
  }
}

小白:哇,这里感觉有点绕呀,为什么read/write实现要这样搞?
小黑:其实我也搞不清楚,说是为了扩展性吧,但是这些都没有对外开放,根本扩展不了啊,我也很迷糊。可以先暂时不管它,如果以后碰到这个问题,我们再回来讨论。你重点看看read/write的实现,看得懂吗?
小白:实现挺简单易懂的,就是将这个结构体的所有字段一一读取出来,就是干这么一件事嘛。
小黑:没错,实际上也就干了这么一件事。这个read方法就算讲完了,没啥难点,所以我们继续下一个吧,你接着说第二个地方。
小白:getResult(iface, args)这个方法我猜是直接调用本地的实现,这个没啥难的,就是我不明白它是如何确定调用哪个接口的,如果iface里面有很多接口,怎么就知道调用哪一个呢?例如

service TCalculate {
    Dto.ExpressionResult calculate(1:Dto.Expression exp) throws (1:Exception.MinSystemException ex);
    string sayHello(1:string msg) throws (1:Exception.MinSystemException ex);
}

小黑:嗯,说到点子上了,我们来看看源码,看看从什么时候就将iface这个参数给透传进去了

//这一开始就将Iface的实现类TCalculateImpl当做参数传进去了
TProcessor tProcessor = new TCalculate.Processor<TCalculate.Iface>(new TCalculateImpl());

……

public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {
    //调用的就是这个构造函数,将实现类传了进去
    public Processor(I iface) {
      super(iface, getProcessMap(new java.util.HashMap<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
    }
    //这里就把对应的ProcessFunction跟接口名对应起来了
    private static <I extends Iface> java.util.Map<java.lang.String,  org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
      processMap.put("calculate", new calculate());
      processMap.put("sayHello", new sayHello());
      return processMap;
    }
	……
}
……
public abstract class TBaseProcessor<I> implements TProcessor {
  private final I iface;
  private final Map<String,ProcessFunction<I, ? extends TBase>> processMap;
  
  protected TBaseProcessor(I iface, Map<String, ProcessFunction<I, ? extends TBase>> processFunctionMap) {
    this.iface = iface;
    this.processMap = processFunctionMap;
  }
  @Override
  public boolean process(TProtocol in, TProtocol out) throws TException {
    TMessage msg = in.readMessageBegin();
    ProcessFunction fn = processMap.get(msg.name);
    ……
    //这里的fn相当于calculate,因为calculate继承自ProcessFunction
    //所以calculate只需要实现getResult,等着在process里面被调用就行了
    fn.process(msg.seqid, in, out, iface);
  }
}
……
public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {
	……
    public static class calculate<I extends Iface> extends org.apache.thrift.ProcessFunction<I, calculate_args> {
      public calculate() {
        super("calculate");
      }
      //这就是process里T args = getEmptyArgsInstance();
      public calculate_args getEmptyArgsInstance() {
        return new calculate_args();
      }
      //实际上就是calculate.getResult()
      public calculate_result getResult(I iface, calculate_args args) throws org.apache.thrift.TException {
        calculate_result result = new calculate_result();
        try {
          //这里就是真正的调用实现类中的方法了
          //相当于TCalculateImpl.calculate(exp)
          result.success = iface.calculate(args.exp);
        } catch (com.acme.service.Exception.MinSystemException ex) {
          result.ex = ex;
        }
        return result;
      }
    }

    public static class sayHello<I extends Iface> extends org.apache.thrift.ProcessFunction<I, sayHello_args> {
      public sayHello() {
        super("sayHello");
      }

      public sayHello_args getEmptyArgsInstance() {
        return new sayHello_args();
      }
      
      public sayHello_result getResult(I iface, sayHello_args args) throws org.apache.thrift.TException {
        sayHello_result result = new sayHello_result();
        try {
          result.success = iface.sayHello(args.msg);
        } catch (com.acme.service.Exception.MinSystemException ex) {
          result.ex = ex;
        }
        return result;
      }
 }

小黑:我的注释很清楚了,这下明白如何区分调用的接口了吧?如何还是有些懵懂,可以自己去代码里面点实现,看看是不是会跳到刚才讲的地方。
小白:明白了,重点是ProcessFunction这个类,所有的接口包装类都实现它,然后每个包装类再实现自己的方法调用即可。哈哈哈,豁然开朗。
小黑:好了,thrift核心的源码就这些了,其它的源码都是围绕这些进行扩展、装饰等,弄懂了我讲的这个流程,其它的源码信手拈来!
小白:哈哈,我也觉得,现在我都觉得我可以自己写个Thrift了。
小黄:喂喂喂,虽然你菜不是你的错,但是你XXX就是你的错了。
小黑:哈哈,小白你还是得好好回去实践一下,光听不练是不行的,你可以自己试试扩展,再高级一点,更换thrift的三大组件,那都是可以玩的。不是那么简单的哦~
小白:哈哈,我就开玩笑,我回去会好好搞的!
小黑:对了,,,Client端的代码太简单我都忘记贴出来了,小白你还要看不?
小白:贴吧贴吧,给个完整的句号
小黑:好吧,说的也是

// 创建 TTransport
TTransport transport = new TSocket("127.0.0.1", 8080, 2000);
// 创建 TProtocol
TProtocol protocol = new TBinaryProtocol(transport);

// 创建Client
TCalculate.Client client = new TCalculate.Client(protocol);

// 连接服务器
transport.open();

// 调用服务方法
ExpressionResult result = client.calculate(new Expression(OPERATE.DIV, 1, 0));
System.out.println(result.getResult());
transport.close();

小黑:好了,thrift专题分享到此结束,小白你回去后总结一下这次讲的重点难点。
小白:好累,明天就阔以!
小黑:嗯,好样的!


第三天……


小黑:怎么样了小白,给我们说说?
小白:好嘞,你们细细听来

  • Thrift本质上是一个网络通信框架,只是在通信的基础上,给了通信协议,以此达到远程接口调用的目的。
  • 当项目需要拆分成微服务时,可以考虑使用RPC来进行服务之间的解耦
  • TTransport相当于Socket,是进行网络通信的。
  • TProtocol相当于定义如何发送和接收数据,是接口调用信息发送接收的规则
  • TProcessor相当于接口调用的处理器。
  • TTransport、TProtocol和TProcessor是可以扩展的,我们可以实现自定义的这三个组件。

小黑:嗯,相当精练,不啰嗦,可以了。
小白:哈哈,那我接着研究研究了~
小黑:去吧~

未完待续……

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值