thrift源码解析之processor


前言

thrift文章整理:
1.thrift简介
2.thrift源码解析之compiler
3.thrift源码解析之processor
4.thrift源码解析之protocol
5.thrift源码解析之transport
6.thrift源码解析之server


概述

协议层和用户提供的服务实现之间的纽带,连接compiler生成的代码和protocol,定义了调用【服务实现】的接口框架,(服务实现是在代码生成器中生成的服务代码)。
例子:一个分布式日志收集系统scribe。本文基于thrift0.13.0版本分析。和参考文章版本不一样。所以部分分析内容也不同。

类图:
在这里插入图片描述
从上图中可以看出TProcessor是这个部分的顶层基类,其他类基本上都是通过Thrift代码生成工具生成的,只有少数是为了扩展一些功能而直接写代码实现,如PeekProcessor类就增加了一些对原始数据处理的功能。scribeProcessor和FacebookServiceProcessor类就是用代码生成器根据IDL文件生成的,也是我们后面需要分析的一个实例。

服务接口调用框架分析

这个基本的框架包括三个类:
1、TProcessor
抽象类,负责调用定义的服务接口,从一个接口读入数据,写到一个输出接口。
一个最主要的函数定义如下:

virtual bool process(std::shared_ptr<protocol::TProtocol> in,
                       std::shared_ptr<protocol::TProtocol> out,
                       void* connectionContext) = 0;

这个函数是一个纯虚函数,所以继承这个类的子类都必须实现这个函数,这个函数就是最主要的数据传输功能。

2、TProcessorEventHandler
负责处理TProcessor类产生的事件。主要定义了一些当某些事件发生时的处理函数,例如当读取参数之前可以做一些处理函数。
这个类中定义的各个成员函数,每一个函数都处理一种事件发生时的情况:

函数名称功能
getContext调用其他回调函数之前调用,期望返回一些有序的上下文对象以便传递给其他回调函数使用
freeContext期望释放一个上下文有关的资源
preRead在读参数以前调用
postRead在读参数和处理函数之间调用
preWrite在处理和写响应之间调用
postWrite在写响应之后调用
asyncComplete当一个异步函数成功完成调用时调用
handlerError如果处理函数抛出没有定义的异常就会调用此函数

3.TProcessorContextFreer
帮助类,帮助生成的代码来释放上下文资源

实例分析:scribe

1.接口定义语言(IDL)

(1)Facebook内部共用服务协议

主要有两个文件:
1、IDL文件
在thrift中定义,是用facebook内部的一些接口服务定义,这个不仅仅用于scribe服务器,可能还用于Facebook内部其他系统,内容如下:

namespace java com.facebook.fb303
namespace cpp facebook.fb303
namespace perl Facebook.FB303

enum fb_status {
  DEAD = 0,
  STARTING = 1,
  ALIVE = 2,
  STOPPING = 3,
  STOPPED = 4,
  WARNING = 5,
}

service FacebookService {
  string getName(),
  string getVersion(),
  fb_status getStatus(),
  string getStatusDetails(),
  map<string, i64> getCounters(),
  i64 getCounter(1: string key),
  void setOption(1: string key, 2: string value),
  string getOption(1: string key),
  map<string, string> getOptions(),
  string getCpuProfile(1: i32 profileDurationInSec),
  i64 aliveSince(),
  oneway void reinitialize(),
  oneway void shutdown(),
}

这个IDL文件定义了一个枚举类型用于表示服务的状态,还定义了一个名为FacebookService的服务,定义了获取服务状态、得到计数的操作等等。
用此IDL文件生成c++代码(Linux执行代码生成命令)

thrift -gen cpp facebook.thrift

看看是什么样的一个架构

(1)首先生成了一个基于上面服务定义的抽象类(接口):
class FacebookServiceIf {
 public:
  virtual ~FacebookServiceIf() {}
  virtual void getName(std::string& _return) = 0;
  virtual void getVersion(std::string& _return) = 0;
  virtual fb_status::type getStatus() = 0;
  virtual void getStatusDetails(std::string& _return) = 0;
  virtual void getCounters(std::map<std::string, int64_t> & _return) = 0;
  virtual int64_t getCounter(const std::string& key) = 0;
  virtual void setOption(const std::string& key, const std::string& value) = 0;
  virtual void getOption(std::string& _return, const std::string& key) = 0;
  virtual void getOptions(std::map<std::string, std::string> & _return) = 0;
  virtual void getCpuProfile(std::string& _return, const int32_t profileDurationInSec) = 0;
  virtual int64_t aliveSince() = 0;
  virtual void reinitialize() = 0;
  virtual void shutdown() = 0;
};

注意,除了多了一个虚析构函数,其他函数都是IDL中定义的。

(2)接着定义了类FacebookServiceNull

这个是上面那个类的空实现,好处:当我们需要重写一些函数的时候只需要关注我们需要的函数,而不用重写所有函数。

class FacebookServiceNull : virtual public FacebookServiceIf {
 public:
  virtual ~FacebookServiceNull() {}
  void getName(std::string& /* _return */) {
    return;
  }
  void getVersion(std::string& /* _return */) {
    return;
  }
  
  ...
};
(3)接着定义了封装每一个函数参数的相应类

一个函数的参数都用一个类来封装定义,函数的返回值也这样处理。比如getName方法如下。这样做的目的是统一远程调用的实现接口,因为传递参数都只需要这个封装类的对象就可以了。

1class FacebookService_getName_args {}2class FacebookService_getName_pargs {}3typedef struct _FacebookService_getName_result__isset {} _FacebookService_getName_result__isset;4class FacebookService_getName_result{}5typedef struct _FacebookService_getName_presult__isset {} _FacebookService_getName_presult__isset;6class FacebookService_getName_presult{}
(4)接下来定义三个具体实现IDL定义的功能的类

1、一个客户端的类FacebookServiceClient
它继承定义的服务抽象类FacebookServiceClient

构造函数接收一个或两个TProtocol类型的参数,对其输入输出进行初始化,并且获得几个变量给接下来的函数使用,变量类型都是TProtocol或者TProtocol*。

首先给出和协议相关的操作函数:

  FacebookServiceClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) {
    setProtocol(prot);
  }
  FacebookServiceClient(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) {
    setProtocol(iprot,oprot);
  }
 private:
  void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> prot) {
  setProtocol(prot,prot);
  }
  void setProtocol(std::shared_ptr< ::apache::thrift::protocol::TProtocol> iprot, std::shared_ptr< ::apache::thrift::protocol::TProtocol> oprot) {
    piprot_=iprot;
    poprot_=oprot;
    iprot_ = iprot.get();
    oprot_ = oprot.get();
  }
 public:
  std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() {
    return piprot_;
  }
  std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() {
    return poprot_;
  }

然后给出函数的发送方法和返回值接收方法:每个函数实现都是同样的方法和思路,比如getName方法:

send_getName();
recv_getName(_return);

由上面代码可以看出首先调用函数发送函数名称及相关信息到远程,然后接受函数调用的返回值
send_getName代码实现:

void FacebookServiceClient::send_getName()
{
  int32_t cseqid = 0;
  oprot_->writeMessageBegin("getName", ::apache::thrift::protocol::T_CALL, cseqid);

  FacebookService_getName_pargs args;
  args.write(oprot_);		//写入参数

  oprot_->writeMessageEnd();
  oprot_->getTransport()->writeEnd();
  oprot_->getTransport()->flush();	//保证这次写入过程立即生效
}

上面代码就完成了函数名称以及参数的传输,调用的是TProtocol相关的函数实现,具体实现会在TProtocol部分介绍。

recv_getName代码实现:

void FacebookServiceClient::recv_getName(std::string& _return)
{

  int32_t rseqid = 0;										//接收的消息序列号
  std::string fname;										//函数名称
  ::apache::thrift::protocol::TMessageType mtype;			//消息类型(调用:T_CALL、异常:T_EXCEPTION等)

  iprot_->readMessageBegin(fname, mtype, rseqid);			//从返回消息读取函数名称、消息类型
  if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {	//处理异常消息
    ::apache::thrift::TApplicationException x;
    x.read(iprot_);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
    throw x;
  }
  if (mtype != ::apache::thrift::protocol::T_REPLY) {		//处理返回消息
    iprot_->skip(::apache::thrift::protocol::T_STRUCT);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
  }
  if (fname.compare("getName") != 0) {						//看是否是我们需要的函数名,不是就跳过消息读取
    iprot_->skip(::apache::thrift::protocol::T_STRUCT);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
  }
  FacebookService_getName_presult result;
  result.success = &_return;
  result.read(iprot_);										//读取函数返回值
  iprot_->readMessageEnd();
  iprot_->getTransport()->readEnd();

  if (result.__isset.success) {								//成功就返回结果(已经在_return里面),否则抛出异常
    // _return pointer has now been filled
    return;
  }
  throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "getName failed: unknown result");
}

上面代码就是处理远程调用的返回结果。这个是用于客户端的处理流程,必须通过有效的机制来通知服务器端调用相应的函数在服务器完成相应功能并将结果返回。
这种机制就是通过我们这部分介绍的TProcessor类实现。

2、一个服务器的类FacebookServiceProcessor
它从TProcessor类继承,主要做了以下几件事:
1、定义一个FacebookServiceIf类的指针iface_
2、实现函数dispatchCall(TDispatchProcessor.h中用到)。
3、定义一个map:std::map<std::string, ProcessFunction> ProcessMap;
   ProcessFunction是void fun(TProtocol*,TProtocol*, void*)类型的函数指针
   定义一个此类型的变量 ProcessMap processMap_;
4、实现函数:process_函数名 x n个
5、构造函数填充map

看dispatchCall具体做了什么,接收五个参数:

bool FacebookServiceProcessor::dispatchCall(::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, const std::string& fname, int32_t seqid, void* callContext) {
  ProcessMap::iterator pfn;
  pfn = processMap_.find(fname);		//client中传过来的函数名fname
  if (pfn == processMap_.end()) {		//遍历完但是没有发现名字相同的函数
    iprot->skip(::apache::thrift::protocol::T_STRUCT);
    iprot->readMessageEnd();
    iprot->getTransport()->readEnd();
    ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, "Invalid method name: '"+fname+"'");//写入(返回)一个异常信息给调用客户端,客户端会根据返回结果处理异常
    oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);
    x.write(oprot);
    oprot->writeMessageEnd();
    oprot->getTransport()->writeEnd();
    oprot->getTransport()->flush();
    return true;
  }
  (this->*(pfn->second))(seqid, iprot, oprot, callContext);		//遍历了map然后执行其中一个process_函数名()函数
  return true;
}

假如执行到以下函数,此函数应该是要去调用service中具体实现的函数了

void FacebookServiceProcessor::process_getName(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext)
{
  void* ctx = NULL;
  if (this->eventHandler_.get() != NULL) {
    ctx = this->eventHandler_->getContext("FacebookService.getName", callContext);
  }
  ::apache::thrift::TProcessorContextFreer freer(this->eventHandler_.get(), ctx, "FacebookService.getName");

  if (this->eventHandler_.get() != NULL) {
    this->eventHandler_->preRead(ctx, "FacebookService.getName");
  }

  FacebookService_getName_args args;
  args.read(iprot);
  iprot->readMessageEnd();
  uint32_t bytes = iprot->getTransport()->readEnd();

  if (this->eventHandler_.get() != NULL) {
    this->eventHandler_->postRead(ctx, "FacebookService.getName", bytes);
  }

  FacebookService_getName_result result;
  try {
    iface_->getName(result.success);
    result.__isset.success = true;
  } catch (const std::exception& e) {
    if (this->eventHandler_.get() != NULL) {
      this->eventHandler_->handlerError(ctx, "FacebookService.getName");
    }

    ::apache::thrift::TApplicationException x(e.what());
    oprot->writeMessageBegin("getName", ::apache::thrift::protocol::T_EXCEPTION, seqid);
    x.write(oprot);
    oprot->writeMessageEnd();
    oprot->getTransport()->writeEnd();
    oprot->getTransport()->flush();
    return;
  }

  if (this->eventHandler_.get() != NULL) {
    this->eventHandler_->preWrite(ctx, "FacebookService.getName");
  }

  oprot->writeMessageBegin("getName", ::apache::thrift::protocol::T_REPLY, seqid);
  result.write(oprot);
  oprot->writeMessageEnd();
  bytes = oprot->getTransport()->writeEnd();
  oprot->getTransport()->flush();

  if (this->eventHandler_.get() != NULL) {
    this->eventHandler_->postWrite(ctx, "FacebookService.getName", bytes);
  }
}

以下是原作者分析的:他的版本应该是在c++11之前的,没用到map遍历的方法,而且接收的参数是三个,fname需要自己去读
重点实现两个函数process和process_fn,其中process会调用process_fn函数来处理客户端具体调用的那个服务函数,process函数定义如下:

::apache::thrift::protocol::TProtocol> piprot, boost::shared_ptr<
::apache::thrift::protocol::TProtocol> poprot, void* callContext) {
::apache::thrift::protocol::TProtocol* iprot = piprot.get();
::apache::thrift::protocol::TProtocol* oprot = poprot.get();
std::string fname; ::apache::thrift::protocol::TMessageType mtype;
int32_t seqid;   iprot->readMessageBegin(fname, mtype,
seqid);//读取得到函数名称、消息类型和函数序列号 //处理不是函数调用消息的情况 if (mtype !=
::apache::thrift::protocol::T_CALL && mtype !=
::apache::thrift::protocol::T_ONEWAY) {
    iprot->skip(::apache::thrift::protocol::T_STRUCT);
    iprot->readMessageEnd();
    iprot->getTransport()->readEnd();
    ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::INVALID_MESSAGE_TYPE);
//写入(返回)一个异常信息给调用客户端,客户端会根据返回结果处理异常
    oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);
    x.write(oprot);
    oprot->writeMessageEnd();
    oprot->getTransport()->writeEnd();
    oprot->getTransport()->flush();
    return true; } return process_fn(iprot, oprot, fname, seqid, callContext);//调用实际的函数处理 } ```

3、FacebookServiceHandler类
骨架文件从FacebookServiceIf继承而来的类,是用户对函数的实现的类,用户需要改的类,实现函数的功能。

总结

client我们得自己实现,而service的框架thrift会帮我们生成,我们只需要在service中提供函数的具体实现。
可以看下下面这个简单的例子,就能清楚的知道其运行过程。

struct Student{
    1: i32 sno,
    2: string sname,
    3: bool ssex,
    4: i16 sage,
}
service Serv{
    void put(1: Student s),
    void print(),
}
thrift -r --gen cpp student.thrift

生成代码之后,我们得自己写一个客户端如下:那么我们有了Serv_server.skeleton.cpp服务器和client.cpp客户端

#include "./gen-cpp/Serv.h"
#include </usr/local/include/thrift/transport/TSocket.h>
#include </usr/local/include/thrift/transport/TBufferTransports.h>
#include </usr/local/include/thrift/protocol/TBinaryProtocol.h>
#include <iostream>
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
 
//using boost::shared_ptr;
 
int main(int argc, char **argv) {
    std::shared_ptr<TSocket> socket(new TSocket("127.0.0.1", 9090)); 
    std::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
    std::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
    transport->open();
 
//*****************添加部分******************
    Student s;
    s.sno = 123;
    s.sname = "xiaoshe";
    s.ssex = 1;
    s.sage = 30;   
    ServClient client(protocol);
    std::cout<<"client send a data"<<std::endl;;
    client.put(s);
//*****************添加部分******************
    transport->close();
    return 0;
}

在服务端添加函数的实现代码:

void put(const Student& s) {
    // Your implementation goes here
    printf("put\n");
    printf("sno=%d sname=%s ssex=%d sage=%d/n", s.sno, s.sname.c_str(), s.ssex, s.sage); //********次行为添加代码********
  }

可以试着编译运行一下:

//编译client.cpp:
g++ -g -o client -Ithrift  client.cpp gen-cpp/Serv.cpp gen-cpp/student_types.cpp gen-cpp/student_constants.cpp   -lthrift
//编译Serv_server.skeleton.cpp:
g++ -g -o server -Ithrift  Serv.cpp student_types.cpp student_constants.cpp Serv_server.skeleton.cpp  -lthrift

client代码比较简单:
1、打开传输:transport->open();
2、client传入一个参数protocol,这个client类就是前面讲的ServClient,接收Tprotocl类参数,这里我们给了TBinaryProtocol就是用的二进制序列化。
3、调用service中支持的函数
4、关闭传输

server代码:
server.serve();传入4个参数,具体的protocol,transport,server等内容会在之后讲解。
在这里插入图片描述

类图:
高亮的部分是今天已经讲了的类或者比较重要的类,余下的类会在之后说明。
在这里插入图片描述
参考文章:
https://www.cnblogs.com/brucewoo/archive/2012/06/03/2532788.html

Thrift源码解析是通过分析Thrift框架的源代码来了解其内部工作原理和实现细节的过程。在这个过程中,我们可以深入了解Thrift框架中各个模块的功能和相互关系,以及具体的实现方式。 引用中提到了一篇博客文章《Thrift源码解析--Transport传输层分析》,该博客对Thrift框架中Transport传输层的实现进行了详细解析。传输层是Thrift框架中负责网络传输和数据序列化的模块,它定义了一系列的传输协议,如TBinaryProtocol、TCompactProtocol等,并提供了相应的传输方式,如TTransport、TServerTransport等。在这篇博客中,作者对Transport传输层的各个子模块进行了介绍,并详细解释了它们的实现原理和使用方法。 引用中提到了另一篇博客文章《Thrift源码解析(一)主要类概述》,该博客主要介绍了Thrift框架中的一些主要类和它们的作用。这些类包括TProcessor、TProtocol、TServer等,它们是Thrift框架中的核心组件,负责处理数据的编码、解码、处理和传输。在这篇博客中,作者通过对这些类的分析,帮助读者了解Thrift框架的整体结构和工作原理。 引用中提到了一个名为thrift-enhancer的工具包,该工具包是对Thrift协议的增强支持。它提供了动态解析IDL并生成参数对象的能力,生成的参数对象可以自动转换为Thrift协议数据,并且支持Thrift与JSON、XML的双向转换。thrift-enhancer的出现为Thrift源码解析提供了更多的工具和扩展功能,帮助开发者更好地理解和使用Thrift框架。 综上所述,Thrift源码解析是通过分析Thrift框架的源代码来了解其内部工作原理和实现细节的过程。通过阅读相关的博客文章和使用辅助工具,可以更深入地了解Thrift框架的各个模块和主要类的功能和实现方式。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值