1. 概述
本文以C++語言為例介紹了thrift RPC的使用方法,包括對象序列化和反序列化,數(shù)據(jù)傳輸和信息交換等。
本文采用了一個示例進行說明,該示例主要完成傳輸(上報日志或者報表)功能,該示例會貫穿本文,內(nèi)容涉及thrift定義,代碼生成,thrift類說明,client編寫方法,server編寫方法等。
關(guān)于Thrift架構(gòu)分析,可參考:Thrift架構(gòu)介紹。
關(guān)于Thrift文件編寫方法,可參考:Thrift使用指南。
關(guān)于Thrift內(nèi)部實現(xiàn)原理,可參考:淺談Thrift內(nèi)部實現(xiàn)原理。
2. 示例描述
假設(shè)我們要使用thrift RPC完成一個數(shù)據(jù)傳輸任務(wù),數(shù)據(jù)格式和PRC接口用一個thrift文件描述,具體如下:
(1) book.thrift,用于描述書籍信息的thrift接口
| 1234567891011121314151617 | //book.thrift, namespacecpp example structBook_Info { 1: i32 book_id, 2: string book_name, 3: string book_author, 4:doublebook_price, 5: string book_publisher, } |
(2) rpc.thrift,client向server傳輸數(shù)據(jù)(上報日志或者報表)的RPC接口
| 12345678910111213 | //rpc.thrift namespacecpp example include"book.thrift" service BookServlet { boolSender(1: list<book.Book_Info> books); onewayvoidSender2(1: list<book.Book_Info> books); } |
說明:該thrift文件定義了一個service,它包含兩個接口,server端需要實現(xiàn)這兩個接口以對client提供服務(wù)。其中,第一個接口函數(shù)是阻塞式的,即要等待server返回值以后才能繼續(xù),另外一個聲明為oneway類型(返回值為void),表明該函數(shù)是非阻塞式的,將數(shù)據(jù)發(fā)給server后不必等待返回結(jié)果,但使用該函數(shù)時,需要考慮server的承受能力,適度的調(diào)整發(fā)送頻率。
3. Thrift文件與生成的代碼對應(yīng)關(guān)系
每個thrift文件會產(chǎn)生四個文件,分別為:${thrift_name}_constants.h,${thrift_name}_constants.cpp,${thrift_name}_types.h,${thrift_name}_types.cpp
對于含有service的thrift文件,會額外生成兩個文件,分別為:${service_name}.h,${service_name}.cpp
對于含有service的thrift文件,會生成一個可用的server樁:${service_name}._server.skeleton.cpp
對于本文中的例子,會產(chǎn)生以下文件:
book_constants.h book_constants.cpp
book_types.h book_types.cpp
rpc_constants.h rpc_constants.cpp
rpc_types.h rpc_types.cpp
BookServlet.h BookServlet.cpp
BookServlet_server.skeleton.cpp
4. Thrift類介紹
Thrift代碼包(位于thrift-0.6.1/lib/cpp/src)有以下幾個目錄:
concurrency:并發(fā)和時鐘管理方面的庫
processor:Processor相關(guān)類
protocal:Protocal相關(guān)類
transport:transport相關(guān)類
server:server相關(guān)類
4.1 Transport類(how is transmitted?)
負責(zé)數(shù)據(jù)傳輸,有以下幾個可用類:
TFileTransport:文件(日志)傳輸類,允許client將文件傳給server,允許server將收到的數(shù)據(jù)寫到文件中。
THttpTransport:采用Http傳輸協(xié)議進行數(shù)據(jù)傳輸
TSocket:采用TCP Socket進行數(shù)據(jù)傳輸
TZlibTransport:壓縮后對數(shù)據(jù)進行傳輸,或者將收到的數(shù)據(jù)解壓
下面幾個類主要是對上面幾個類地裝飾(采用了裝飾模式),以提高傳輸效率。
TBufferedTransport:對某個Transport對象操作的數(shù)據(jù)進行buffer,即從buffer中讀取數(shù)據(jù)進行傳輸,或者將數(shù)據(jù)直接寫入buffer
TFramedTransport:同TBufferedTransport類似,也會對相關(guān)數(shù)據(jù)進行buffer,同時,它支持定長數(shù)據(jù)發(fā)送和接收。
TMemoryBuffer:從一個緩沖區(qū)中讀寫數(shù)據(jù)
4.2 Protocol類(what is transmitted?)
負責(zé)數(shù)據(jù)編碼,主要有以下幾個可用類:
TBinaryProtocol:二進制編碼
TJSONProtocol:JSON編碼
TCompactProtocol:密集二進制編碼
TDebugProtocol:以用戶易讀的方式組織數(shù)據(jù)
4.3 Server類(providing service for clients)
TSimpleServer:簡單的單線程服務(wù)器,主要用于測試
TThreadPoolServer:使用標(biāo)準(zhǔn)阻塞式IO的多線程服務(wù)器
TNonblockingServer:使用非阻塞式IO的多線程服務(wù)器,TFramedTransport必須使用該類型的server
5. 對象序列化和反序列化
Thrift中的Protocol負責(zé)對數(shù)據(jù)進行編碼,因而可使用Protocol相關(guān)對象進行序列化和反序列化。
由于對象序列化和反序列化不設(shè)計傳輸相關(guān)的問題,所以,可使用TBinaryProtocol和TMemoryBuffer,具體如下:
(1) 使用thrift進行對象序列化
//對對象object進行序列化,保存到str中
123456789101112131415 template<typenameType>voidObject2String(Type& object, string &str) {shared_ptr<TMemoryBuffer> membuffer(newTMemoryBuffer());shared_ptr<TProtocol> protocol(newTBinaryProtocol(membuffer));object.write(protocol.get());str.clear();str = membuffer.getBufferAsString();}(2)使用thrift進行對象反序列化
123456789101112131415 //對str中保存的對象進行反序列化,保存到object中template<typenameType>voidString2Object(string& buffer, Type &object) {shared_ptr<TMemoryBuffer> membuffer(newTMemoryBuffer(reinterpret_cast<uint*>(buffer.data())));shared_ptr<TProtocol> protocol(newTBinaryProtocol(membuffer));object.read(protocol.get());}6. 編寫client和server
6.1 client端代碼編寫
Client編寫的方法分為以下幾個步驟:
(1) 定義TTransport,為你的client設(shè)置傳輸方式(如socket, http等)。
(2) 定義Protocal,使用裝飾模式(Decorator設(shè)計模式)封裝TTransport,為你的數(shù)據(jù)設(shè)置編碼格式(如二進制格式,JSON格式等)
(3) 實例化client對象,調(diào)用服務(wù)接口。
說明:如果用戶在thrift文件中定義了一個叫${server_name}的service,則會生成一個叫${server_name}Client的對象,比如,我給出的例子中,thrift會自動生成一個叫BookServletClient的類,Client端的代碼編寫如下:
123456789101112131415161718192021222324252627282930313233 #include " gen-cpp/BookServlet.h" //一定要包含該頭文件//其頭文件,其他using namespace …….intmain(intargc,char** argv) {shared_ptr<TTransport> socket(newTSocket("localhost", 9090));shared_ptr<TTransport> transport(newTBufferedTransport(socket));shared_ptr<TProtocol> protocol(newTBinaryProtocol(transport));example::BookServletClient client(protocol);try{transport->open();vector<example::Book_Info> books;…...client.Sender(books);//RPC函數(shù),調(diào)用serve端的該函數(shù)transport->close();}catch(TException &tx) {printf("ERROR: %s/n", tx.what());}}6.2 Server端代碼編寫
(1) 定義一個TProcess,這個是thrift根據(jù)用戶定義的thrift文件自動生成的類
(2) 使用TServerTransport獲得一個TTransport
(3) 使用TTransportFactory,可選地將原始傳輸轉(zhuǎn)換為一個適合的應(yīng)用傳輸(典型的是使用TBufferedTransportFactory)
(4) 使用TProtocolFactory,為TTransport創(chuàng)建一個輸入和輸出
(5) 創(chuàng)建TServer對象(單線程,可以使用TSimpleServer;對于多線程,用戶可使用TThreadPoolServer或者TNonblockingServer),調(diào)用它的server()函數(shù)。
說明:thrift會為每一個帶service的thrift文件生成一個簡單的server代碼(樁),在例子中,thrift會生成BookServlet_server.skeleton.cpp,用戶可以在這個文件基礎(chǔ)上實現(xiàn)自己的功能。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 #include "gen-cpp/BookServlet.h"#include <protocol/TBinaryProtocol.h>#include <server/TSimpleServer.h>#include <transport/TServerSocket.h>#include <transport/TBufferTransports.h>usingnamespace::apache::thrift;usingnamespace::apache::thrift::protocol;usingnamespace::apache::thrift::transport;usingnamespace::apache::thrift::server;usingboost::shared_ptr;usingnamespaceexample;classBookServletHandler :virtualpublicBookServletIf {public:BookServletHandler() {// Your initialization goes here}//用戶需實現(xiàn)這個接口boolSender(conststd::vector<example::Book_Info> & books) {// Your implementation goes hereprintf("Sender/n");}//用戶需實現(xiàn)這個接口voidSender2(conststd::vector<example::Book_Info> & books) {// Your implementation goes hereprintf("Sender2/n");}};intmain(intargc,char**argv) {intport = 9090;shared_ptr<BookServletHandler> handler(newBookServletHandler());shared_ptr<TProcessor> processor(newBookServletProcessor(handler));shared_ptr<TServerTransport> serverTransport(newTServerSocket(port));shared_ptr<TTransportFactory> transportFactory(newTBufferedTransportFactory());shared_ptr<TProtocolFactory> protocolFactory(newTBinaryProtocolFactory());TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);server.serve();return0;}7. 總結(jié)
至此,關(guān)于thrift框架的三篇文章已經(jīng)全部完成,包括:
(1) Thrift框架介紹: Thrift框架介紹
(2) Thrift文件編寫方法: Thrift使用指南
(3) Thrift RPC使用方法:利用Thrift RPC編寫程序
與thrift類似的開源RPC框架還有g(shù)oogle的protocal buffer,它雖然支持的語言比較少,但效率更高,因而受到越來越多的關(guān)注。
由于thrift開源時間很早,經(jīng)受了時間的驗證,因而許多系統(tǒng)更愿意采用thrift,如Hadoop,Cassandra等。
附:thrift與protocal buffer比較
從上面的比較可以看出,thrift勝在“豐富的特性“上,而protocal buffer勝在“文檔化”非常好上。在具體實現(xiàn)上,它們非常類似,都是使用唯一整數(shù)標(biāo)記字段域,這就使得增加和刪除字段與不會破壞已有的代碼。
它們的最大區(qū)別是thrift支持完整的client/server RPC框架,而protocal buffer只會產(chǎn)生接口,具體實現(xiàn),還需要用戶做大量工作。
另外,從序列化性能上比較,Protocal Buffer要遠遠優(yōu)于thrift,具體可參考:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/?ca=drs-tp4608 。
新聞熱點
疑難解答