文章目录
前言
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方法如下。这样做的目的是统一远程调用的实现接口,因为传递参数都只需要这个封装类的对象就可以了。
(1)class FacebookService_getName_args {…}
(2)class FacebookService_getName_pargs {…}
(3)typedef struct _FacebookService_getName_result__isset {…} _FacebookService_getName_result__isset;
(4)class FacebookService_getName_result{…}
(5)typedef struct _FacebookService_getName_presult__isset {…} _FacebookService_getName_presult__isset;
(6)class 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