http://dongxicheng.org/tag/thrift/
http://dongxicheng.org/search-engine/thrift-internals/
Thrift由两部分组成:编译器(在compiler目录下,采用C++编写)和服务器(在lib目录下),其中编译器的作用是将用户定义的thrift文件编译生成对应语言的代码,而服务器是事先已经实现好的、可供用户直接使用的RPC Server(当然,用户也很容易编写自己的server)。同大部分编译器一样,Thrift编译器(采用C++语言编写)也分为词法分析、语法分析等步骤,Thrift使用了开源的flex和Bison进行词法语法分析(具体见thrift.ll和thrift.yy),经过语法分析后,Thrift根据对应语言的模板(在compilercppsrcgenerate目录下)生成相应的代码。对于服务器实现而言,Thrift仅包含比较经典的服务器模型,比如单线程模型(TSimpleServer),线程池模型(TThreadPoolServer)、一个请求一个线程(TThreadedServer)和非阻塞模型(TNonblockingServer)等。本文将以C++为例进行一个实例分析。
假设用户编写了以下Thrift文件:
1 struct LogInfo { 2 1: required string name, 3 2: optional string content, 4 } 5 service LogSender { 6 void SendLog(1:list<LogInfo> loglist); 7 }
用户使用命令“thrift –gen cpp example.thrift”可生成C++代码,该代码包含以下文件:
example_constants.h example_constants.cpp example_types.h //struct定义 example_types.cpp //struct实现 LogSender.h //service定义 LogSender.cpp //service实现和LogSenderClient实现 LogSender_server.skeleton.cpp //一个实例RPC Server
用户可以这样编写Client:
1 shared_ptr socket(new TSocket(“8.8.8.8″, 9090)); 2 shared_ptr transport(new TBufferedTransport(socket)); 3 shared_ptr protocol(new TBinaryProtocol(transport)); 4 LogSenderClient client(protocol); 5 try { 6 transport->open(); 7 vector<LogInfo> logInfos; 8 LogInfo logInfo(“image”, “10:9:0 visit:xxxxxx”); 9 logInfos.push_back(logInfo); 10 ….. 11 client.SendLog(logInfos); 12 transport->close(); 13 } catch (TException &tx) { 14 printf(“ERROR: %s ”, tx.what()); 15 }
为了深入分析这段代码,我们看一下client.SendLog()函数的内部实现(在LogSender.cpp中):
1 void LogSenderClient::SendLog(const std::vector<LogInfo> & loglist) 2 { 3 send_SendLog(loglist); 4 recv_SendLog(); 5 } 6 void LogSenderClient::send_SendLog(const std::vector<LogInfo> & loglist) 7 { 8 int32_t cseqid = 0; 9 oprot_->writeMessageBegin(“SendLog”, ::apache::thrift::protocol::T_CALL, cseqid); 10 LogSender_SendLog_pargs args; 11 args.loglist = &loglist; 12 args.write(oprot_); 13 oprot_->writeMessageEnd(); 14 oprot_->getTransport()->flush(); 15 oprot_->getTransport()->writeEnd(); 16 } 17 void LogSenderClient::recv_SendLog() 18 { 19 int32_t rseqid = 0; 20 std::string fname; 21 ::apache::thrift::protocol::TMessageType mtype; 22 iprot_->readMessageBegin(fname, mtype, rseqid); 23 if (mtype == ::apache::thrift::protocol::T_EXCEPTION) { 24 ….. 25 } 26 if (mtype != ::apache::thrift::protocol::T_REPLY) { 27 …… 28 } 29 if (fname.compare(“SendLog”) != 0) { 30 …… 31 } 32 LogSender_SendLog_presult result; 33 result.read(iprot_); 34 iprot_->readMessageEnd(); 35 iprot_->getTransport()->readEnd(); 36 return; 37 }
阅读上面的代码,可以看出,RPC函数SendLog()实际上被转化成了两个函数:send_SendLog和recv_SendLog,分别用于发送数据和接收结果。数据是以消息的形式表示的,消息头部是RPC函数名,消息内容是RPC函数的参数。
我们再进一步分析RPC Server端,一个server的编写方法(在LogSender.cpp中)如下:
1 shared_ptr protocolFactory(new TBinaryProtocolFactory()); 2 shared_ptr handler(new LogSenderHandler()); 3 shared_ptr processor(new LogSenderProcessor(handler)); 4 shared_ptr serverTransport(new TServerSocket(9090)); 5 shared_ptr transportFactory(new TBufferedTransportFactory()); 6 TSimpleServer server(processor, 7 serverTransport, 8 transportFactory, 9 protocolFactory); 10 printf(“Starting the server… ”); 11 server.serve();
Server端最重要的类是LogSenderProcessor,它内部有一个映射关系processMap_,保存了所有RPC函数名到函数实现句柄的映射,对于LogSender而言,它只保存了一个RPC映射关系:
processMap_[" SendLog"] = &LogSenderProcessor::process_SendLog;
其中,process_SendLog是一个函数指针,它的实现如下:
1 void LogSenderProcessor::process_SendLog(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot) 2 { 3 LogSender_SendLog_args args; 4 args.read(iprot); 5 iprot->readMessageEnd(); 6 iprot->getTransport()->readEnd(); 7 LogSender_SendLog_result result; 8 try { 9 iface_->SendLog(args.loglist);//调用用户编写的函数 10 } catch (const std::exception& e) { 11 …… 12 } 13 oprot->writeMessageBegin(“SendLog”, ::apache::thrift::protocol::T_REPLY, seqid); 14 result.write(oprot); 15 oprot->writeMessageEnd(); 16 oprot->getTransport()->flush(); 17 oprot->getTransport()->writeEnd(); 18 }
LogSenderProcessor中一个最重要的函数是process(),它是服务器的主体函数,服务器端(socket server)监听到客户端有请求到达后,会检查消息类型,并检查processMap_映射,找到对应的消息处理函数,并调用之(注意,这个地方可以采用各种并发模型,比如one-request-one-thread,thread pool等)。
通过上面的分析可以看出,Thrift最重要的组件是编译器(采用C++编写),它为用户生成了网络通信相关的代码,从而大大减少了用户的编码工作。