這個thrift的簡單示例, 來源於官網 (http://thrift.apache.org/tutorial/cpp), 因為我覺得官網的例子已經很簡單了, 所以沒有寫新的示例, 關於安裝的教程, 可以參考https://www.cnblogs.com/albizzia/p/10838646.html, 關於thrift文件的語法, 可以參考: https://www.cnblogs.com/albizzia/p/10838646.html.
thrift文件
首先給出shared.thrift文件的定義:
/**
* 這個Thrift文件包含一些共享定義
*/ namespace cpp shared struct SharedStruct { 1: i32 key 2: string value } service SharedService { SharedStruct getStruct(1: i32 key) }
然后給出tutorial.thrift的定義:
/**
* Thrift引用其他thrift文件, 這些文件可以從當前目錄中找到, 或者使用-I的編譯器參數指示.
* 引入的thrift文件中的對象, 使用被引入thrift文件的名字作為前綴, 例如shared.SharedStruct.
*/
include "shared.thrift"
namespace cpp tutorial
// 定義別名
typedef i32 MyInteger
/**
* 定義常量. 復雜的類型和結構體使用JSON表示法.
*/
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
/**
* 枚舉是32位數字, 如果沒有顯式指定值,從1開始.
*/
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
/**
* 結構體由一組成員來組成, 一個成員包括數字標識符, 類型, 符號名, 和一個可選的默認值.
* 成員可以加"optional"修飾符, 用來表明如果這個值沒有被設置, 那么他們不會被串行化到
* 結果中. 不過這個在有些語言中, 需要顯式控制.
*/
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
// 結構體也可以作為異常
exception InvalidOperation {
1: i32 whatOp,
2: string why
}
/**
* 服務需要一個服務名, 加上一個可選的繼承, 使用extends關鍵字
*/
service Calculator extends shared.SharedService {
/**
* 方法定義和C語言一樣, 有返回值, 參數或者一些它可能拋出的異常, 參數列表和異常列表的
* 寫法與結構體中的成員列表定義一致.
*/
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* 這個方法有oneway修飾符, 表示客戶段發送一個請求, 然后不會等待回應. Oneway方法
* 的返回值必須是void
*/
oneway void zip()
}
將上述文件放置在同一個文件夾, 然后編譯上述的thrift文件:
$ thrift -r --gen cpp tutorial.thrift
生成的文件會出現在gen-cpp子文件夾中, 因為編譯時使用了-r參數, 所以shared.thrift也會被編譯.
我們可以考慮查看一下thrift編譯之后生成的文件, 這里, 我們可以考慮先編譯shared.thrift, 編譯后, 會生成7個文件, 分別是shared_constants.h, shared_constants.cpp, shared_types.h, shared_types.cpp, SharedService.h, SharedService.cpp, SharedService_server.skeleton.cpp.
我們先查看shared_constants.h和shared.constants.cpp, 這兩個文件的命名是原先的thrift文件名, 加上_constants, 換種方式說, 就是xxx.thrift會生成xxx_constants.h和xxx_constants.cpp. 查看一下這兩個文件中的內容: 其中會有一個類叫做xxxConstants, 在這個類中, 會將常量作為公有成員, 然后可以通過一個全局變量g_xxx_constants訪問. 而xxx_constants.cpp為類函數的定義, 以及全局變量的定義, 應該比較容易理解.
關於shared_types.h和shared_types.cpp文件, 查看shared_types.h中的內容可以看出, shared_types.h中是thrift文件中各種類型的定義, 這個根據文件名應該可以大致猜出. 其中每一個結構體對應兩部分, 假設這個結構體叫yyy, 那么第一個部分是結構體_yyy__isset,這個結構體會為thrift中yyy的每個字段添加一個對應的bool值, 名字相同. 第二部分是結構體yyy. 這個結構體中包括thrift中yyy的每個字段, 加上_yyy__isset對象. 這個對象用於yyy讀取輸入給自身賦值時, 標識某個字段是否被賦值. yyy中的函數主要有如下幾個: (1) 默認構造函數 (2) 析構函數 (3) 設置成員變量值的函數 (4) 比較函數 (5) read函數, 用來讀取TProtocol對象的內容, 來給自己賦值 (6) write函數, 將自身的值寫入到TProtocol的對象中. 最后還有一個swap函數.
關於SharedService.h和SharedService.cpp文件, 查看SharedService.h中的內容可以看出, 這個文件的文件名來自於thrift中的service SharedService, 假設服務名叫做zzz, 那么就會生成對應的zzz.h和zzz.cpp文件, 用來實現這個服務的接口相關的內容. 查看SharedService.h文件, 可以看到如下內容:
(1) class SharedServiceIf用來實現thrift文件中SharedService的接口,
(2) SharedServiceIfFactory用來實現SharedServiceIf的工廠接口, 構建函數為getHandler, 釋放函數為releaseHandler, 其中SharedServiceIf在工廠類中定義別名Handler.
(3) SharedServiceIfSingletonFactory是SharedServiceIfFactory的一個具體實現, 用來實現返回單例的SharedServiceIf對象.
(4) SharedServiceNull是SharedServiceIf的不做任何行為的實現.
(5) _SharedService_getStruct_args__isset是SharedService服務的getStruct函數的參數對應的isset類, 用來表示這些參數是否存在.
(6) SharedService_getStruct_args是SharedService服務的getStruct函數的參數對應的類, 用來表示一個服務的函數的參數, 實現內容和thrift文件中的結構體的實現基本一致.
(7) SharedService_getStruct_pargs用處不太清楚.
(8) _SharedService_getStruct_result__isset是SharedService服務的getStruct函數的返回值對應的isset函數, 用來表示返回值是否設置.
(9) SharedService_getStruct_result是SharedService服務的getStruct函數的返回值對應的類, 用來表示一個服務的函數的返回值.
(10) _SharedService_getStruct_presult__isset和SharedService_getStruct_presult用處不太清楚.
(11) SharedServiceClient 是thrift中SharedService服務的客戶端實現. SharedServiceClient包括以下內容:
1) 構造函數
2) 獲取輸入和輸出Protocol的函數
3) 服務中定義的方法, 這里是getStruct函數, 以及getStruct函數實現的兩個函數,
void SharedServiceClient::getStruct(SharedStruct& _return, const int32_t key) { send_getStruct(key); recv_getStruct(_return); }
(12) SharedServiceProcessor為編譯器自動生成的對象, 位於Protocol層之上, Server層之下, 實現從輸入protocol中讀取數據, 然后交給具體的Handler處理, 然后再將結果寫入到輸出protocol中. 關於這些聯系可以參考 https://www.cnblogs.com/albizzia/p/10884907.html.
(13) SharedServiceProcessorFactory是SharedServiceProcessor的工廠類.
(14) SharedServiceMultiface是SharedServiceIf的具體實現, 實現了類似於chain of responsiblity的效果, 也就是依次調用構造函數中傳入的多個
SharedServiceIf對象的對應方法. 參考代碼:
void getStruct(SharedStruct& _return, const int32_t key) { size_t sz = ifaces_.size(); size_t i = 0; for (; i < (sz - 1); ++i) { ifaces_[i]->getStruct(_return, key); } ifaces_[i]->getStruct(_return, key); return; }
關於SharedService_server.skeleton.cpp文件, 假設thrift中定義的服務名叫做zzz, 那么這個文件名叫做zzz_server.skeleton.cpp, skeleton的含義是框架, 這個文件的作用是告訴我們如何寫出thrift服務器的代碼. 這個文件包括兩部分:
(1) 類SharedServiceHandler, 這個類用來實現SharedServiceIf, 假設thrift中的服務名叫做zzz, 那么這個類的名字就相對的叫做zzzHandler. 這個類會給出如果你想要實現SharedServiceIf, 那么你需要實現的具體的函數, 對於這個類來說, 需要實現構造函數和getStruct函數, getStruct函數是服務中定義的函數, 有時候, 你也需要實現析構函數吧.
(2) 然后是一個main函數, main函數中的內容, 告訴你怎樣實現一個簡單的thrift服務器. 你可以考慮把這個文件拷貝一份, 然后根據這個拷貝進行修改, 實現服務器的功能.
如果把shared.thrift和tutorial.thrift一起編譯, 那么就會出現14個文件, 每個thrift文件對應7個, 文件的布局和作用和之前說明的一致.
服務端代碼
#include <thrift/concurrency/ThreadManager.h> #include <thrift/concurrency/PlatformThreadFactory.h> #include <thrift/protocol/TBinaryProtocol.h> #include <thrift/server/TSimpleServer.h> #include <thrift/server/TThreadPoolServer.h> #include <thrift/server/TThreadedServer.h> #include <thrift/transport/TServerSocket.h> #include <thrift/transport/TSocket.h> #include <thrift/transport/TTransportUtils.h> #include <thrift/TToString.h> #include <thrift/stdcxx.h> #include <iostream> #include <stdexcept> #include <sstream> #include "../gen-cpp/Calculator.h" using namespace std; using namespace apache::thrift; using namespace apache::thrift::concurrency; using namespace apache::thrift::protocol; using namespace apache::thrift::transport; using namespace apache::thrift::server; using namespace tutorial; using namespace shared; class CalculatorHandler : public CalculatorIf { public: CalculatorHandler() {} void ping() { cout << "ping()" << endl; } int32_t add(const int32_t n1, const int32_t n2) { cout << "add(" << n1 << ", " << n2 << ")" << endl; return n1 + n2; } int32_t calculate(const int32_t logid, const Work& work) { cout << "calculate(" << logid << ", " << work << ")" << endl; int32_t val; switch (work.op) { case Operation::ADD: val = work.num1 + work.num2; break; case Operation::SUBTRACT: val = work.num1 - work.num2; break; case Operation::MULTIPLY: val = work.num1 * work.num2; break; case Operation::DIVIDE: if (work.num2 == 0) { InvalidOperation io; io.whatOp = work.op; io.why = "Cannot divide by 0"; throw io; } val = work.num1 / work.num2; break; default: InvalidOperation io; io.whatOp = work.op; io.why = "Invalid Operation"; throw io; } SharedStruct ss; ss.key = logid; ss.value = to_string(val); log[logid] = ss; return val; } void getStruct(SharedStruct& ret, const int32_t logid) { cout << "getStruct(" << logid << ")" << endl; ret = log[logid]; } void zip() { cout << "zip()" << endl; } protected: map<int32_t, SharedStruct> log; }; /* CalculatorIfFactory is code generated. CalculatorCloneFactory is useful for getting access to the server side of the transport. It is also useful for making per-connection state. Without this CloneFactory, all connections will end up sharing the same handler instance. */ class CalculatorCloneFactory : virtual public CalculatorIfFactory { public: virtual ~CalculatorCloneFactory() {} virtual CalculatorIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) { stdcxx::shared_ptr<TSocket> sock = stdcxx::dynamic_pointer_cast<TSocket>(connInfo.transport); cout << "Incoming connection\n"; cout << "\tSocketInfo: " << sock->getSocketInfo() << "\n"; cout << "\tPeerHost: " << sock->getPeerHost() << "\n"; cout << "\tPeerAddress: " << sock->getPeerAddress() << "\n"; cout << "\tPeerPort: " << sock->getPeerPort() << "\n"; return new CalculatorHandler; } virtual void releaseHandler( ::shared::SharedServiceIf* handler) { delete handler; } }; int main() { TThreadedServer server( stdcxx::make_shared<CalculatorProcessorFactory>(stdcxx::make_shared<CalculatorCloneFactory>()), stdcxx::make_shared<TServerSocket>(9090), //port stdcxx::make_shared<TBufferedTransportFactory>(), stdcxx::make_shared<TBinaryProtocolFactory>()); /* // if you don't need per-connection state, do the following instead TThreadedServer server( stdcxx::make_shared<CalculatorProcessor>(stdcxx::make_shared<CalculatorHandler>()), stdcxx::make_shared<TServerSocket>(9090), //port stdcxx::make_shared<TBufferedTransportFactory>(), stdcxx::make_shared<TBinaryProtocolFactory>()); */ /** * Here are some alternate server types... // This server only allows one connection at a time, but spawns no threads TSimpleServer server( stdcxx::make_shared<CalculatorProcessor>(stdcxx::make_shared<CalculatorHandler>()), stdcxx::make_shared<TServerSocket>(9090), stdcxx::make_shared<TBufferedTransportFactory>(), stdcxx::make_shared<TBinaryProtocolFactory>()); const int workerCount = 4; stdcxx::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(workerCount); threadManager->threadFactory( stdcxx::make_shared<PlatformThreadFactory>()); threadManager->start(); // This server allows "workerCount" connection at a time, and reuses threads TThreadPoolServer server( stdcxx::make_shared<CalculatorProcessorFactory>(stdcxx::make_shared<CalculatorCloneFactory>()), stdcxx::make_shared<TServerSocket>(9090), stdcxx::make_shared<TBufferedTransportFactory>(), stdcxx::make_shared<TBinaryProtocolFactory>(), threadManager); */ cout << "Starting the server..." << endl; server.serve(); cout << "Done." << endl; return 0; }
上述代碼應該很容易理解, 在這里就不解釋了.
客戶端代碼
#include <iostream> #include <thrift/protocol/TBinaryProtocol.h> #include <thrift/transport/TSocket.h> #include <thrift/transport/TTransportUtils.h> #include <thrift/stdcxx.h> #include "../gen-cpp/Calculator.h" using namespace std; using namespace apache::thrift; using namespace apache::thrift::protocol; using namespace apache::thrift::transport; using namespace tutorial; using namespace shared; int main() { stdcxx::shared_ptr<TTransport> socket(new TSocket("localhost", 9090)); stdcxx::shared_ptr<TTransport> transport(new TBufferedTransport(socket)); stdcxx::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); CalculatorClient client(protocol); try { transport->open(); client.ping(); cout << "ping()" << endl; cout << "1 + 1 = " << client.add(1, 1) << endl; Work work; work.op = Operation::DIVIDE; work.num1 = 1; work.num2 = 0; try { client.calculate(1, work); cout << "Whoa? We can divide by zero!" << endl; } catch (InvalidOperation& io) { cout << "InvalidOperation: " << io.why << endl; // or using generated operator<<: cout << io << endl; // or by using std::exception native method what(): cout << io.what() << endl; } work.op = Operation::SUBTRACT; work.num1 = 15; work.num2 = 10; int32_t diff = client.calculate(1, work); cout << "15 - 10 = " << diff << endl; // Note that C++ uses return by reference for complex types to avoid // costly copy construction SharedStruct ss; client.getStruct(ss, 1); cout << "Received log: " << ss << endl; transport->close(); } catch (TException& tx) { cout << "ERROR: " << tx.what() << endl; } }
從上面的客戶端調用來看, 方法調用和本地的類對象的調用很相似, thrift的設計算是很巧妙的. 里面的代碼應該不復雜, 所以也不進行具體的講解了.
查看一下CMakeLists.txt文件:
cmake_minimum_required(VERSION 2.8) #include_directories(SYSTEM "${Boost_INCLUDE_DIRS}") #Make sure gen-cpp files can be included include_directories("${CMAKE_CURRENT_BINARY_DIR}") include_directories("${CMAKE_CURRENT_BINARY_DIR}/gen-cpp") include_directories("${PROJECT_SOURCE_DIR}/lib/cpp/src") set(tutorialgencpp_SOURCES gen-cpp/Calculator.cpp gen-cpp/SharedService.cpp gen-cpp/shared_constants.cpp gen-cpp/shared_types.cpp gen-cpp/tutorial_constants.cpp gen-cpp/tutorial_types.cpp ) add_library(tutorialgencpp STATIC ${tutorialgencpp_SOURCES}) target_link_libraries(tutorialgencpp thrift) add_custom_command(OUTPUT gen-cpp/Calculator.cpp gen-cpp/SharedService.cpp gen-cpp/shared_constants.cpp gen-cpp/shared_types.cpp gen-cpp/tutorial_constants.cpp gen-cpp/tutorial_types.cpp COMMAND ${THRIFT_COMPILER} --gen cpp -r ${PROJECT_SOURCE_DIR}/tutorial/tutorial.thrift ) add_executable(TutorialServer CppServer.cpp) target_link_libraries(TutorialServer tutorialgencpp) if (ZLIB_FOUND) target_link_libraries(TutorialServer ${ZLIB_LIBRARIES}) endif () add_executable(TutorialClient CppClient.cpp) target_link_libraries(TutorialClient tutorialgencpp) if (ZLIB_FOUND) target_link_libraries(TutorialClient ${ZLIB_LIBRARIES}) endif ()
編譯運行, 我這邊啟動客戶端和服務端的命令分別是:
$ LD_LIBRARY_PATH=/usr/local/lib ./TutorialServer
$ LD_LIBRARY_PATH=/usr/local/lib ./TutorialClient
注: 上述代碼可以在thrift源代碼中的tutorial/cpp文件夾找到.