【引子】
在英語裡,thrift是個名詞,表示的是“節儉、節約”,給個例句會印象更深些:
Farmers know a lot about value and thrift。(譯:農場主深諳價值與節儉之道。)
然而,這篇文章並非是一篇英語學習教程,而是要和大家介紹計算機技術裡一款著名的通訊框架 – thrift框架。
好,現在有請thrift登場。
【thrift是什麼】
thrift的全名叫做Apache thrift,是一款軟體開發RPC框架,可以很高效地實現跨語言的RPC服務。
如果你還不瞭解RPC是什麼,趕快看看這裡。
如果想參觀參觀thrift的官方網站,請點選這裡。
【thrift生於何地】
thrift最初生於Facebook,並茁壯成長,在2007年由Facebook正式開源出來,2008年由Apache軟體基金會開始負責專案孵化直至今日。
【還有哪些RPC框架】
protobuf、Avro、MessagePack等,如果你有興趣,可以搜尋一下他們,也有利於你更好的瞭解RPC這個領域的發展情況。
【下載thrift】
目前的最新版本是thrift-0.9.1
【安裝thrift】
首先建議你安裝如下這些軟體包:
1 2 |
automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel |
然後從官網下載thrift原始碼包,並進行編譯連結:
1 2 3 |
./configure –-prefix=/your/program/path/ --enable-libtool-lock make make install |
安裝完成後,你會看到thrift其實包含了三部分:一個bin程式、一坨標頭檔案和若干庫檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#小知識 ls使用-F選項的話,檔案列表中會用符號表示檔案屬性, #如*表示可執行檔案,@表示軟連線,/表示資料夾 [rocrocket@li218-69 thrift]$ ls -1F bin thrift* [rocrocket@li218-69 thrift]$ ls -1F lib libthrift-0.9.1.so* libthrift.a libthrift.la* libthriftnb-0.9.1.so* libthriftnb.a libthriftnb.la* libthriftnb.so@ libthrift.so@ libthriftz-0.9.1.so* libthriftz.a libthriftz.la* libthriftz.so@ pkgconfig/ [rocrocket@li218-69 thrift]$ ls -1F include/thrift/ async/ concurrency/ config.h cxxfunctional.h processor/ protocol/ qt/ server/ TApplicationException.h TDispatchProcessor.h thrift-config.h Thrift.h TLogging.h TProcessor.h transport/ TReflectionLocal.h |
【為什麼需要thrift】
如果你之前沒有接觸過RPC框架的話,可能理解起來會比較困難。為了照顧這些新使用者的感受,我嘗試著用一種好理解的思路來解釋:
研發工程師小吳接到了一個新任務,給“托福考試成績資料庫”增加一個“成績查詢”的功能,客戶端提供“使用者ID”向伺服器端發起查詢請求,伺服器端接到查詢請求後從資料庫中取回此使用者ID對應的姓名和成績,並返回給客戶端。
就是這樣一個簡單的Client-Server通訊過程,其實就形成了一個典型的RPC場景。伺服器端提供“成績查詢服務”,客戶端會通過約定的方法來查詢成績。
小吳設計的方法呼叫和資料傳輸是這樣的:
thrift example
通過上圖可以看到,伺服器端處於監聽狀態(等待請求的到來),客戶端發起一個名為Search的動作,引數是使用者ID,而這個動作的返回是一個結構體struct UserGradeInfo,其中包含了使用者的名字(UserName)和使用者的成績(UserGrade)。
設計做完了,小吳要開始編碼了。如果按照“手工作坊”的思路,小吳至少需要完成如下幾個方面:
(1)“客戶端向伺服器端傳送資料”的程式碼
(2)“客戶端接收伺服器端查詢結果”的程式碼
(3)“伺服器端接收客戶端資料”的程式碼
(4)“伺服器端向客戶端傳送查詢結果”的程式碼
(5)如果客戶端會大批量發起查詢,那可能還需要考慮改成多執行緒模型或非同步模型
(6)而且還有可能因為某種原因,要求客戶端和伺服器端使用不同的語言進行開發
照此思路,小吳至少要3周時間來開發和自測。(時間很長,對吧)
但,自從thrift出現後(更準確的說,是自從RPC開發框架出現後),上述工作量被大大簡化了。我們只要呼叫一個thrift工具就可以自動生成上述的所有程式碼,即便伺服器端和客戶端使用不同的語言,thrift也照樣支援。
至此,我相信你應該大概理解thrift能幫我們做什麼了吧。
【thrift到底怎麼用】
依然拿上面的“成績資料庫”的例子來說,thrift的使用可以被分為四步:
第1步: 明確要互動的資料格式(如上例中的UserGradeInfo)和具體的方法(如上例中的Search),定義出thrift介面描述檔案(英文叫做Inteface Description File);
第2步: 呼叫thrift工具,依據thrift介面檔案,生成RPC程式碼;
第3步: 你的伺服器端程式引用thrift生成的RPC程式碼,並實現其中的Search動作的邏輯,然後啟動監聽,等待客戶端發來請求。
第4步: 客戶端同樣引入並呼叫RPC程式碼來與伺服器端通訊;
(如果你覺得這樣描述太空虛,別急,稍後會有完整的例子)
【thrift介面描述檔案怎麼編寫】
如果你是學院派,那麼我推薦你研究thrift IDL(Interface Definition Language)規範,在這裡。雖然有些晦澀,但你可以從中瞭解到一個介面檔案可以如何來寫。
在編寫介面檔案時,需要對你要傳輸的資料設定資料型別,比如UserName是字串型,UserGrade是整型等。因為thrift是支援眾多開發語言的,所以thrift提供了一套自己的資料型別編寫規範,只有用這套獨立於任何語言的型別規範來編寫介面檔案,thrift才能把它轉換成你指定的那種開發語言的程式碼。
thrift中的型別包括基礎型別、結構、容器、異常、服務等幾個部分。(官網中有專門介紹資料型別的頁面,在這裡)
【型別 之 基礎型別】
基礎型別,其實非常簡單和明確:
(1)bool:布林型別(true或false)
(2)byte:8位有符號整數
(3)i16:16位有符號整數
(4)i32:32位有符號整數
(5)i64:64位有符號整數
(6)double:64位浮點數
(7)string:文字字串,使用UTF-8編碼
(有些細心的同學會詢問“為什麼不支援無符號整數型別呢?”,這是因為在很多開發語言中並沒有原生的無符號整型。)
【型別 之 容器】
thrift容器包括了各種語言中最常用的容器,共三種:
(1)list容器:一個元素可重複的有序列表。會被轉換成C++中的vector,Java中的ArrayList,指令碼語言中的陣列等。
(2)set容器:一個元素不可重複的無序集合。會轉換成C++中的set,Java中的HashSet、Python中的Set等。(熟悉PHP的同學可能會問“PHP並不支援set型別,怎麼辦”,在PHP語言中,thrift會將set容器轉換成List。)
(3)map容器:一個含有多個key:value鍵值對的結構。會被轉換成C++中的map,Java中的HashMap,PHP中的關聯陣列,Python/Ruby中的dictionary等。
對於上述三種容器,其元素的型別原則上可以是任何一種thrift型別。但是值得注意的是,map的key型別需要是基礎型別,因為很多開發語言並不支援map的key型別為複雜資料型別。
【型別 之 結構體】
結構體型別,在形式上和C/C++中的結構體型別非常相似,就是一坨型別的組合,比如上文圖中的UserGradeInfo便是一個thrift結構體型別。
thrift介面檔案中的結構體型別,都會被轉換成一個獨立的類(Class)。類的屬性便是結構體中的各個型別,而類的方法便是對這些型別進行處理的相關函式。
我們來看一個結構體定義的例子:
1 2 3 4 5 6 7 8 9 |
struct UserGradeInfo { 1: required string UserName = "Anonymous"; 2: required i16 UserGrade = 0; } |
可以看到,結構體中每一個域都有一個正整數識別符號,這個識別符號並不要求連續,但一旦定義,不建議再進行修改。
另外,每個域前都會有required或optional的限定,前者表示是必填域,後者則表示是可選域。域是可以有預設值的,比如上例中的“Anonymous”和0。
(1)如果一個域設定了required,但是在實際構造結構體時又沒有給這個域賦值,那麼thrift會認為這是一個異常。
(2)如果一個域設定為optional且在構造結構體時沒有給這個域賦值,那麼在使用這個結構體時,就會忽略掉這個optional的域。
【型別 之 異常】
除了使用exception來替代struct以外,“異常”這個型別,在語法上和剛才介紹過的結構體的用法是完全一致的。但是從語義上講,exception和struct卻大相徑庭。exception是在遠端呼叫發生異常時用來丟擲異常用的。
【型別 之 服務】
服務的定義,與物件導向技術中定義一個介面很類似,而這些介面其實就是純虛擬函式。thrift編譯工具會根據服務的定義來產生相應的方法和函式。
每個服務,都包括了若干個函式,每個函式包括了若干引數和一個返回值(返回值可以是void)。
(小技巧:返回值為void的函式,你可以在函式名前加上oneway識別符號,將此函式以非同步模式執行,這樣在呼叫此函式後,函式會立即返回。)
對於返回void的函式,thrift仍然會確保函式返回,這樣就表示這個函式已被正確執行,且伺服器端已有返回資訊了。但是如果給void的函式前加上oneway,那麼此函式的返回只能表示資料已經進入傳輸層,並不能表示伺服器端已經收到並返回了資料。
【我們來看一個thrift介面描述檔案的例子吧】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# 例子 - thrift介面描述檔案 # # 編寫這個檔案是為了教會你如何寫thrift介面描述檔案。 # 第一個你應該掌握的知識點就是.thrift檔案 # 支援shell的註釋方式,那就是用#符號。 /** * 我們首先來複習一下thrift的常用資料型別,如下所示: * * bool 布林型,1個位元組 * byte 有符號整數,1個位元組 * i16 有符號16位整型 * i32 有符號32位整型 * i64 有符號64位整型 * double 64位浮點數值 * string 字串型別 * binary 二進位制資料型別(位元組陣列) * list 單型別有序列表,允許有重複元素 * set 單型別無需集合,不允許有重複元素 * map<t1,t2> Map型(key:value) * * 你發現了麼,.thrift檔案還支援C語言的多行註釋形式。 */ // 不賣關子了,其實我們還支援C語言的單行註釋形式呢 ^_^ /** * .thrift檔案可以引用其他.thrift檔案,這樣就可以方便地把一些公共結構和服務囊括進來。 * 在引用其他.thrift檔案時,既可以直接引用當前資料夾下的檔案,也可以引用其他路徑下的 * 檔案,但後者需要在thrift編譯工具編譯時加上-I選項來設定路徑。 * * 如果希望訪問被包含的.thrift檔案中的內容,則需要使用.thrift檔案的檔名作為字首, * 比如shared.SharedObject。我們在本例中引用了檔案shared.thrift。 */ include "shared.thrift" /** * Thrift支援對.thrift檔案中的型別設定namespace,這樣可以有效避免名字衝突。 * 這種機制在C++中也叫做namespace,而在Java中叫做Package。 * thrift支援針對不同的語言設定不同的namespace,比如下面的例子。 * thrift會在生成不同語言程式碼時,進行相應的設定。 */ namespace cpp tutorial namespace go tutorial namespace java tutorial namespace php tutorial namespace perl tutorial /** * thrift還可以使用typedef來給型別起別名。 */ typedef i32 MyInteger /** * Thrift也支援定義常量。 * 對於結構複雜的常量,支援使用JSON形式來表示。 */ const i32 MY_NUM = 9853 const map<string,string> MY_MAP = {'hello':'world', 'goodnight':'moon'} /** * 你還可以定義列舉型別, 其被指定為32位整型。域的值是可以自定義的,而且 * 當不提供域的值時,預設會從1開始編號並遞增。 */ enum Operation { ADD = 1, SUBTRACT = 2, MULTIPLY = 3, DIVIDE = 4 } /** * 結構體則是一個複雜的資料型別。它由多個域組成,每個域會對應一個整數識別符號, * 每一行的格式為:一個冒號,一個型別,一個域名稱和一個(非必填的)預設值。 * * 每個域都可以設定為optional或required來表示是否為必填域,以便thrift決定是否 * 在資料傳輸時要包含這個域。不指定時,預設為required。 */ struct Work { 1: i32 num1 = 0, 2: i32 num2, 3: Operation op, 4: optional string comment, } /** * 在語法上,異常的定義方式和結構體是完全一樣的。在發生問題時,可以丟擲異常。 */ exception InvalidOperation { 1: i32 what, 2: string why } /** * 啊哈,我們現在到了最Cool的環節,即定義服務。 * (一個服務可以使用extends來繼承另一個服務。) */ service Calculator extends shared.SharedService { /** * 服務中方法的定義非常類似於C語言的語法。它會包括一個返回值, * 一個引數列表以及一個可以丟擲的異常列表(可選) * 可以提前告訴大家的是,定義引數列表的方法、定義異常列表的方法, * 和定義結構體的方法都是相似的,可以從下面的例子中看出。 * 除了最後一個方法,其他的方法最後都要有一個逗號,大家可不要忽略這個細節。 */ void ping(), i32 add(1:i32 num1, 2:i32 num2), /** * 在異常列表前,需要加throws關鍵字。 */ i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), /** * 如下的這個方法有一個oneway修飾符,還記得他的作用麼 * 這表示這個方法在呼叫後會立即返回,不會等待遠端的回覆。 * 要注意的是,oneway只能修飾void返回型別。 * oneway在英語裡就是“單向”的意思,還是很形象滴。 */ oneway void zip() } /** * 在你使用thrift編譯工具編譯此檔案後, * 會在當前目錄產生一個“gen-<你選擇的開發語言>” * 資料夾,比如你選擇的是C++語言,則會產生gen-cpp資料夾, * 裡面放著的便是thrift幫你生成好的程式碼, * 程式碼並不那麼晦澀,你可以開啟看一看。 */ |
【使用thrift編譯工具】
在我們編寫好thrift介面描述檔案之後,thrift編譯工具就要派上用場了,它的作用就是根據thrift介面描述檔案來生成相應開發語言的RPC程式碼,以便使用者可以在自己的程式中呼叫。
thrift編譯工具的名稱就是thrift,其最常見的使用方式是這樣的:
1 2 3 4 5 6 |
thrift --gen ${開發語言} ${thrift介面描述檔案} # 執行了上述命令之後,就會在當前資料夾下生成一個以“gen-${開發語言}”命名的資料夾, # 裡面便是自動生成的程式碼。 |
【thrift會自動生成哪些程式碼呢】
在編譯之後,thrift會生成這些檔案:(我們以mytime.thrift為例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
$ cd gen-cpp $ ls -1 mytime_types.* mytime_types.cpp mytime_types.h # 和資料型別有關的內容,會命名為 # ${thrift_file_name}_types.h/${thrift_file_name}_types.cpp。 # 比如你使用typedef定義了型別別名, # 或者你定義了一個struct型別,都會在這兩個檔案中記錄。 # 對於struct型別,有必要多說一下,thrift會針對每一個struct型別 # 生成一個對應的類,類中會包括一個建構函式、一個解構函式、 # 域變數定義、用於設定域值的__set_XXX()方法、 # 過載比較符(==,!=,<)、設定讀寫此結構體的方法read/write, # 以及一個用於表示域是否設定了值的_${struct name}__isset結構體。 # 另外,還會有一個獨立定義的swap方法用來進行兩個結構體的值交換。 $ ls -1 mytime_constants.* mytime_constants.cpp mytime_constants.h # 和常量有關的內容,會命名為 # ${thrift_file_name}_constants.h/${thrift_file_name}_constants.cpp。 # 在.h標頭檔案中會有一個${thrift_file_name}Constants類, # 其中會包括一個顯式的建構函式,以及常量的域。 # 而在cpp檔案中則會在相應的建構函式中對這個常量進行賦值。 $ ls -1 myservice* myservice.cpp myservice.h myservice_server.skeleton.cpp # 針對每一個service會產生一套對應的檔案,命名為 # ${service_name}.cpp/${service_name}.h/${service_name}_server.skeleton.cpp, # 在${service_name}.h中會看到有若干個類,他們都是以服務名作為字首的, # 其中包括${service_name}If、${service_name}IfFactory、 # ${service_name}IfSingletonFactory、${service_name}Null、 # ${service_name}Client、${service_name}Processor、 # ${service_name}ProcessorFactory、${service_name}Multiface。 # 另外,thrift還會針對服務中的每一個具體的方法分別產生四個對應的類,即 # ${service_name}_${method}_args、${service_name}_${method}_pargs、 # ${service_name}_${method}_result、${service_name}_${method}_presult。 # 另外,${service_name}_server.skeleton.cpp是一個server的模板例子。 |
【一起用thrift來做個專案!】
從我的學習經驗來看,框架的學習路線是“瞭解應用場景 -> 瞭解用法 -> 看例子 -> 深入使用者 -> 自己寫例子”。我相信,如果你能和我一起走完這個例子,一定會消除對thrift的恐懼,愛上這款RPC框架的。
我們的例子很簡單,就是一個“時間問答”機器人,英文叫做WhatTime,客戶會向伺服器端詢問現在幾點啦,伺服器端會把現在的時間回答給客戶端。就像這樣:
1 2 3 |
客戶端:請問,現在幾點啦? 伺服器端:現在是上午10點01分。 |
我們會在伺服器端使用C++來實現,而在客戶端會使用C++語言來實現一版,還會使用最近很流行的Go語言實現一版。(Go語言可是未來可能撼動IT界的語言之一哦)
thrift介面描述檔案WhatTime.thrift:
1 2 3 4 5 |
namespace cpp roctime service TimeService { i32 TellMeTime() } |
需要經過thrift編譯工具編譯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ thrift --gen cpp WhatTime.thrift $ ls -1F gen-cpp/ WhatTime.thrift $ cd gen-cpp/ $ ls -1 TimeService.cpp TimeService.h TimeService_server.skeleton.cpp WhatTime_constants.cpp WhatTime_constants.h WhatTime_types.cpp WhatTime_types.h |
然後,我們把server的樣例檔案重新命名一下:
1 |
$ mv TimeService_server.skeleton.cpp server.cpp |
我們將server.cpp中的TellMeTime方法做一些修改,加入報告時間的邏輯:
1 2 3 4 5 |
int32_t TellMeTime() { // Your implementation goes here time_t now_time = time(NULL); return now_time; } |
好了,server.cpp完工,我們對server.cpp進行編譯連結:
1 2 3 4 5 |
g++ -I /home/roc/program/thrift/include -c TimeService.cpp g++ -I /home/roc/program/thrift/include -c WhatTime_constants.cpp g++ -I /home/roc/program/thrift/include -c WhatTime_types.cpp g++ -I /home/roc/program/thrift/include -c server.cpp g++ -L /home/roc/program/thrift/lib/ TimeService.o WhatTime_constants.o WhatTime_types.o server.o -o server -lthrift |
如果提示找不到thrift動態連結庫,那就需要把thrift的lib路徑(如/home/roc/program/thrift/lib)加入到ld.so.conf中,然後執行ldconfig命令在重新將動態連結庫裝載到cache中。
然後就可以直接執行./server了,可以看到9090埠開啟,已經開始服務了。
下面,我們繼續編寫客戶端的程式碼。thrift並沒有給出客戶端的程式碼樣例,所以需要自己來開發。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "TimeService.h" #include <thrift/transport/TSocket.h> #include <thrift/transport/TBufferTransports.h> #include <thrift/protocol/TBinaryProtocol.h> #include <iostream> using namespace std; using namespace apache::thrift; using namespace apache::thrift::protocol; using namespace apache::thrift::transport; using namespace roctime; int main(int argc, char *argv[]) { boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090)); boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket)); boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); time_t mytime = 0; TimeServiceClient client(protocol); transport->open(); mytime = client.TellMeTime(); cout << "Now is " << ctime(&mytime) << endl; transport->close(); return 0; } |
開發完成後,我們對client進行編譯連結:
1 2 3 4 5 |
g++ -I /home/roc/program/thrift/include -c TimeService.cpp g++ -I /home/roc/program/thrift/include -c WhatTime_constants.cpp g++ -I /home/roc/program/thrift/include -c WhatTime_types.cpp g++ -I /home/roc/program/thrift/include -c client.cpp g++ -L /home/roc/program/thrift/lib/ TimeService.o WhatTime_constants.o WhatTime_types.o client.o -o client -lthrift |
好了,伺服器端程式server和客戶端程式client都生成好了,可以試著執行這個例子:
在一個終端執行伺服器端程式:
1 |
$ ./server |
在另一個終端開啟客戶端程式:
1 2 |
$ ./client Now is Fri Nov 1 12:14:06 2013 |
順利的話,你應該可以看到執行server的終端視窗會輸出“Now is Fri Nov 1 12:14:06 2013”啦!RPC通訊成功了!
至此,C++版本的客戶端和伺服器端都已經實現了。是不是並沒有那麼的難呢!下面,我們來看看Go語言的客戶端如何實現。
【Go語言版客戶端】
首先通過thrift來生成go的程式碼:
1 |
$ thrift -gen go WhatTime.thrift |
會生成gen-go資料夾,進入其中,可以看到這裡面有什麼東東:
1 2 3 |
$ cd gen-go/ $ ls -1F WhatTime/ |
在gen-go資料夾中,我們作如下的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#有人會問為什麼要搞個src資料夾呢,稍後你就會知道答案 $ mkdir src #將thrift自動生成的WhatTime資料夾移動到src中 $ mv WhatTime src/ #將當初原始碼安裝thrift時的資料夾(也就是tar.gz解包後的資料夾)中的lib/go/thrift拷貝到src中。 #拷貝過來的thrift資料夾中全都是.go檔案,這些便是thrift支援go語言的庫檔案,用於我們稍後編譯連結所用。 $ cp -r /path/to/source/thrift/lib/go/thrift/ . #都完成後,我們看看gen-go/src資料夾中的目錄結構 $ ls -1F thrift/ WhatTime/ #設定GOPATH全域性變數,以便Go語言能查詢到所需的包 #當我們設定了GOPATH之後,Go語言會預設在$GOPATH/src下來查詢相應的包, #這下你應該明白為什麼當初要建立這個src資料夾了吧。 #(畫外音:除了上述方法,Go語言還支援使用相對路徑來引用一個包。) export GOPATH="$GOPATH:/path/to/gen-go" #除此之外,你還要確保Go語言已經正確安裝,且PATH和GOROOT也已正確設定。 export PATH="/path/to/go_dir/bin:${PATH}" export GOROOT=""/path/to/go_dir" |
下一步,我們需要對$GOPATH/src/WhatTime中的constants.go、time_service.go和ttypes.go三個檔案做一下小的修改:
1 2 3 4 5 6 7 |
我們將import區域中的 "git.apache.org/thrift.git/lib/go/thrift" 修改為 "thrift" 作如上修改的原因,其一是我們已經在本地準備好了支援thrift的go語言包, 其二是因為這樣可以避免在無法連線到網際網路的情況下,程式編譯失效。 |
說實話,Go語言的準備工作確實有些繁瑣,希望你還有耐心看最關鍵的內容,那就是編寫client.go的程式碼!
我們在src的同級目錄中來編寫,client.go的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package main import ( "fmt" "time" "thrift" "WhatTime" ) func handleClient(client *WhatTime.TimeServiceClient) (err error) { t,_ := client.TellMeTime() fmt.Println(time.Unix(int64(t), 0).String()) return nil } func runClient(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string) error { var transport thrift.TTransport transport, err := thrift.NewTSocket(addr) if err != nil { fmt.Println("Error opening socket:", err) return err } transport = transportFactory.GetTransport(transport) defer transport.Close() if err := transport.Open(); err != nil { return err } return handleClient(WhatTime.NewTimeServiceClientFactory(transport, protocolFactory)) } func main() { var protocolFactory thrift.TProtocolFactory protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() var transportFactory thrift.TTransportFactory transportFactory = thrift.NewTBufferedTransportFactory(1024) addr := "localhost:9090" if err := runClient(transportFactory, protocolFactory, addr); err != nil { fmt.Println("error running client:", err) } } |
然後進行編譯連結和執行:
1 2 3 |
$ go build client.go $ ./client 2013-11-01 12:37:31 +0800 CST |
至此,我們的Go語言版本也大功告成了!
【結語】
如果你耐心地看到了這裡,說明你完成了thrift的入門。但是“紙上得來終覺淺,自己動手才是真”。
後面還會有Thrift進階篇,敬請期待。