Apache Thrift的簡單使用
----------------------
1. 簡單介紹
Thrift是Facebook的一個開源項目,主要是一個跨語言的服務開發框架。它有一個代碼生成器來對它所定義的IDL定義文件自動生成服務代碼框架。用戶只要在其之前進行二次開發就行,對於底層的RPC通訊等都是透明的。目前它支持的語言有C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk, and OCaml.
2. 下載與安裝
可以在http://incubator.apache.org/thrift/download/去下載它的最新版本,目前最新版本是0.5.0。另外你也可以check出它的svn,方法如下:
svn co http://svn.apache.org/repos/asf/thrift/trunk thrift
cd thrift
在它的jira中看到,它的0.6版本也很快就會出來了。
我的本本是debian 6.0,如果用ubuntu的兄弟安裝方法也是一樣的
- tar -zxvf thrift-0.5.0.tar.gz
- cd thrift-0.5.0
- ./configure
- make
- sudo make install
這時thrift的代碼生成器和一些庫文件就生成好了。
你可以用如下命令看一下thrift的版本信息
- thrift -version
3. 一個簡單的例子
在thrift源代碼目錄有一個叫tutorial的目錄,進行其中後運行thrift命令生成相應的服務代碼:
- $ thrift -r --gen cpp tutorial.thrift // -r對其中include的文件也生成服務代碼 -gen是生成服務代碼的語言
運行完之後會在當前目錄看到一個gen-cpp目錄,其中就是thrfit命令生成的代碼
這時你cd到tutorial/cpp目錄,運行make,生成相應的CppServer與CppClient程式。
這時你可以用./CppServer運行服務端,讓其監聽一個特定的端口
這時你可以用./CppClient運行客戶端程式,讓其去連接服務端,調用其所對應的服務。默認調用後會輸出如下信息:
- lemo@debian:~/Workspace/Facebook/Thrift/thrift-0.5.0/tutorial/cpp$ ./CppServer
- Starting the server...
- ping()
- add(1,1)
- calculate(1,{4,1,0})
- calculate(1,{2,15,10})
- getStruct(1)
如果你的終端中也出現瞭如上的信息,恭喜你,運行成功了。如果在運行CppServer的時候找不到動態庫,看看你是不是運行了make install,如果運行了,再運行一下sudo ldconfig試試。再用ldd CppServer看一下它有沒有找到相應的動態庫了。
4. 例子分析
4.1 Thrift IDL的分析
這邊有兩個IDL文件,內容如下:
- shared.thrift
- ---------------
- **
- * This Thrift file can be included by other Thrift files that want to share
- * these definitions.
- */
- namespace cpp shared
- namespace java shared
- namespace perl shared
- // 這裏定義了一個結構體,沒有定義方法,對應於生成的代碼在gen-cpp中的shared_types.h中,其中有一個class叫SharedStruct,
- // 有沒有看到其中有兩個方法叫read和write,這就是用來對其進行序列化與把序列化的方法.
- // 對了,其中的i32是Thrift IDL中定義的變量類型,對應於c++語言中的int32_t
- struct SharedStruct {
- 1: i32 key
- 2: string value
- }
- // 這裏定義的一個服務,它語義上類似於面向對象中的定義一個接口,thrift的編譯器會對其產生一套實現其接口的客戶端與服務端方法
- // 服務的一般定義格式如下
- // service <name>
- // <returntype> <name>(<arguments>)
- // [ throws (<exceptions>)]
- // ...
- // }
- service SharedService {
- SharedStruct getStruct(1: i32 key)
- }
- tutorial.thrift
- ----------------
- /**
- * Thrift files can reference other Thrift files to include common struct
- * and service definitions. These are found using the current path, or by
- * searching relative to any paths specified with the -I compiler flag.
- *
- * Included objects are accessed using the name of the .thrift file as a
- * prefix. i.e. shared.SharedObject
- */
- // 這個IDL包含了另一個IDL,也就是說另一個IDL中的對象與服務對其時可見的
- include "shared.thrift"
- /**
- * Thrift files can namespace, package, or prefix their output in various
- * target languages.
- */
- // 這裏定義了一些語言的namespace空間
- namespace cpp tutorial
- namespace java tutorial
- namespace php tutorial
- namespace perl tutorial
- /**
- * Thrift lets you do typedefs to get pretty names for your types. Standard
- * C style here.
- */
- // 自定義類型
- typedef i32 MyInteger
- /**
- * Thrift also lets you define constants for use across languages. Complex
- * types and structs are specified using JSON notation.
- */
- // 定義一些變量
- const i32 INT32CONSTANT = 9853
- const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
- /**
- * You can define enums, which are just 32 bit integers. Values are optional
- * and start at 1 if not supplied, C style again.
- */
- // 定義枚舉類型
- enum Operation {
- ADD = 1,
- SUBTRACT = 2,
- MULTIPLY = 3,
- DIVIDE = 4
- }
- /**
- * Structs are the basic complex data structures. They are comprised of fields
- * which each have an integer identifier, a type, a symbolic name, and an
- * optional default value.
- *
- * Fields can be declared "optional", which ensures they will not be included
- * in the serialized output if they aren't set. Note that this requires some
- * manual management in some languages.
- */
- struct Work {
- 1: i32 num1 = 0,
- 2: i32 num2,
- 3: Operation op,
- 4: optional string comment, //這裏的optional字段類型表示如果這個字段的值沒有被賦值,它就不會被序列化輸出
- }
- /**
- * Structs can also be exceptions, if they are nasty.
- */
- // 這裏定義了一些異常
- exception InvalidOperation {
- 1: i32 what,
- 2: string why
- }
- /**
- * Ahh, now onto the cool part, defining a service. Services just need a name
- * and can optionally inherit from another service using the extends keyword.
- */
- // 這裏是定義服務,它繼承了shared的服務
- service Calculator extends shared.SharedService {
- /**
- * A method definition looks like C code. It has a return type, arguments,
- * and optionally a list of exceptions that it may throw. Note that argument
- * lists and exception lists are specified using the exact same syntax as
- * field lists in struct or exception definitions.
- */
- void ping(),
- i32 add(1:i32 num1, 2:i32 num2),
- i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
- /**
- * This method has a oneway modifier. That means the client only makes
- * a request and does not listen for any response at all. Oneway methods
- * must be void.
- */
- oneway void zip()
- }
4.2 服務端與客戶端代碼的分析
4.2.1 c++服務端
在tutorial/cpp目錄中的CppServer.cpp是它的服務代碼,主要分成兩部分,
一部分是main方法用於做一些初始化與服務的啓動,第二部分對於IDL中定義的接口的實現
- int main(int argc, char **argv) {
- // 定義了RPC的協議工廠,這裏使用了二進制協議,你不可以使用別的協議,如JSON,Compact等
- shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
- // 這裏生成用戶實現的CalculatorHandler服務,再把其幫定到一個Processor上去,它主要用於處理協議的輸入與輸出流
- shared_ptr<CalculatorHandler> handler(new CalculatorHandler());
- shared_ptr<TProcessor> processor(new CalculatorProcessor(handler));
- // 生成一個傳輸通道,這裏使用了Socket方式
- shared_ptr<TServerTransport> serverTransport(new TServerSocket(9090));
- // 生成一個傳輸工廠,主要用於把上面的transport轉換成一個新的應用層傳輸通道
- shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
- // 生成一個簡單的服務端,這是一個單線程的服務端
- TSimpleServer server(processor,
- serverTransport,
- transportFactory,
- protocolFactory);
- // 你也可以生成一個多線程的服務端,就是對其加入線程池。但它現在還不支持進程池,但可能會在0.7版本中進行支持。
- /**
- * Or you could do one of these
- shared_ptr<ThreadManager> threadManager =
- ThreadManager::newSimpleThreadManager(workerCount);
- shared_ptr<PosixThreadFactory> threadFactory =
- shared_ptr<PosixThreadFactory>(new PosixThreadFactory());
- threadManager->threadFactory(threadFactory);
- threadManager->start();
- TThreadPoolServer server(processor,
- serverTransport,
- transportFactory,
- protocolFactory,
- threadManager);
- TThreadedServer server(processor,
- serverTransport,
- transportFactory,
- protocolFactory);
- */
- printf("Starting the server.../n");
- server.serve(); // 啓動服務
- printf("done./n");
- return 0;
- }
另一部分如下:
- // 這一部分主要是實現接口類,用於提供給相應的服務使用。
- class CalculatorHandler : public CalculatorIf {
- public:
- CalculatorHandler() {}
- void ping() {
- printf("ping()/n");
- }
- int32_t add(const int32_t n1, const int32_t n2) {
- ...
- }
- int32_t calculate(const int32_t logid, const Work &work) {
- ...
- }
- void getStruct(SharedStruct &ret, const int32_t logid) {
- ...
- }
- void zip() {
- printf("zip()/n");
- }
- protected:
- map<int32_t, SharedStruct> log;
- };
4.2.2 c++客戶端
- int main(int argc, char** argv) {
- // 生成一個Socket連接到服務端
- shared_ptr<TTransport> socket(new TSocket("localhost", 9090));
- // 對Socket通道加入緩衝功能
- shared_ptr<TTransport> transport(new TBufferedTransport(socket));
- // 生成相應的二進制協議,這個要和服務端一致,不然會出現協議版本不對的錯誤
- shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
- // 生成客戶端的服務對象
- CalculatorClient client(protocol);
- try {
- transport->open(); // 加開服務
- // 調用事先定義好的服務接口
- client.ping();
- printf("ping()/n");
- int32_t sum = client.add(1,1);
- printf("1+1=%d/n", sum);
- Work work;
- work.op = Operation::DIVIDE;
- work.num1 = 1;
- work.num2 = 0;
- try {
- int32_t quotient = client.calculate(1, work);
- printf("Whoa? We can divide by zero!/n");
- } catch (InvalidOperation &io) {
- printf("InvalidOperation: %s/n", io.why.c_str());
- }
- work.op = Operation::SUBTRACT;
- work.num1 = 15;
- work.num2 = 10;
- int32_t diff = client.calculate(1, work);
- printf("15-10=%d/n", diff);
- // Note that C++ uses return by reference for complex types to avoid
- // costly copy construction
- SharedStruct ss;
- client.getStruct(ss, 1);
- printf("Check log: %s/n", ss.value.c_str());
- // 關閉服務
- transport->close();
- } catch (TException &tx) {
- printf("ERROR: %s/n", tx.what());
- }
- }
4.2.3 其它代碼的實現
在tutorial目錄中有其它代碼的例子,如erl,java,python,perl,ruby等。
5 參考
1. http://incubator.apache.org/thrift/
2. http://incubator.apache.org/thrift/static/thrift-20070401.pdf