和 Thrift 的一場美麗邂逅

一. 與 Thrift 的初識

也許大多數人接觸 Thrift 是從序列化開始的。每次搜索 “java序列化” + “方式”、“對比” 或 “性能” 等關鍵字時,搜索引擎總是會返回一大堆有關各種序列化方式的使用方法或者性能對比的結果給你,而其中必定少不了 Thrift,并且其性能還不錯嘞,至少比那戰斗力只有1的渣渣 java 原生序列化要強很多(好吧原諒我的小情緒……)。

然而,我最初接觸 Thrift 卻是從公司的一個項目開始。

也就在去年的這個時候,我所在事業部發現幾個 UGC 社區的小廣告特別嚴重,Boss 要求所有社區必須接入公司的富媒體監控系統(負責公司所有業務的內容審核、處罰工作,以下簡稱監控系統),以實現?UGC 內容(包括文本、圖片、音視頻以及用戶頭像、昵稱等UserInfo)的準實時上報與垃圾信息的自動處理(如清理現場、賬號封禁等)。出于對業務服務的最小侵入、功能復用和流程統一等原則的考慮,抽象出介于業務系統和監控系統之間的接入系統,統一負責對數據的接收、上報、重推、搜索、結果查詢以及對監控系統處罰指令的轉發。該業務可簡單抽象成圖 1.1:

圖 1.1


由于監控系統使用 Thrift 提供服務,因此接入系統與監控系統之間的交互都使用 Thrift 協議。考慮到接入的便捷性,業務系統可以使用 Thrift 和 Http 兩種協議與接入系統交互。

當時是我一個人負責這個項目,由于對 Thrift 的認識還是0,且項目時間短,所以總體上項目是非常趕的,一開始以為自己難以在規定時間內完成,但想不到 Thrift 開發起來還真的是相當的便捷。系統按時上線了,至今也沒出什么幺蛾子。后來又通過學習進一步了解了 Thrift,深以為是個必須入手的技能。

好吧,至此算是和 Thrift 正式結緣了。

二. 所謂的 RPC

在了解 Thrift 之前,先來簡單科普一下什么是 RPC(遠程過程調用)。

先看下面這個栗子:

publicvoidinvoke(){

????String param1 = "my String 1";

????String param2 = "my String 2";

????String res = getStr(param1, param2);

????System.out.println("res="+ res)

}

privateString getStr(String str1, String str2){

????returnstr1 + str2;

}

這是一個最簡單不過的本地函數調用代碼,調用方和被調用方都在一個程序內部,屬于進程內調用。

CPU 在執行調用時切換去執行被調用函數,執行完后再切換回來執行后續的代碼。對調用方而言,執行被調用函數時會阻塞(非異步情況下)直到調用函數執行完畢。過程如圖 2.1

圖 2.1


接下來看個 RPC 調用的栗子:

publicvoidtest(){

????TestQry.Client client = getClient("192.168.4.222", 7800, 5000);

????String param1 = "my String 1";

????String param2 = "my String 2";

????String res = client.getStr(param1, param2);

????System.out.println("res="+ res);

}

privateTestQry.Client getClient(String ip, intport, inttimeOut) throwsException{

????TSocket tSocket = newTSocket();

????TTransport transport = newTFramedTransport(tSocket);

????tTransport.open();

????TProtocol protocol = newTBinaryProtocol(tTransport);

????returnnewTestQry.Client(protocol);

}

這是一個進程間調用,調用方和被調用方不在同一個進程(甚至不同的服務器或機房)。

進程間調用需要通過網絡來傳輸數據,調用方在執行 RPC 調用時會阻塞(非異步情況下)直到調用結果返回才繼續執行后續代碼。過程如圖 2.2

圖 2.2


一言以蔽之,RPC 是一種通過網絡從遠程計算機程序上請求服務的方式,它使得開發包括網絡分布式多程序在內的應用程序更加容易。

三. 不僅僅是個序列化工具

Thrift最初是由 Facebook 開發用做系統內各語言之間的 RPC 通信的一個可擴展且跨語言的軟件框架,它結合了功能強大的軟件堆棧和代碼生成引擎,允許定義一個簡單的定義文件中的數據類型和服務接口,以作為輸入文件,編譯器生成代碼用來方便地生成RPC客戶端和服務器通信的無縫跨編程語言。

Thrift 是 IDL 描述性語言的一個具體實現,適用于程序對程序靜態的數據交換,需要先確定好數據結構。

Thrift 是完全靜態化的,當數據結構發生變化時,必須重新編輯IDL文件、代碼生成再編譯載入的流程,跟其他IDL工具相比較可以視為是 Thrift 的弱項。Thrift 適用于搭建大型數據交換及存儲的通用工具,在大型系統中的內部數據傳輸上相對于 JSON 和 XML 無論在性能、傳輸大小上有明顯的優勢。

注意, Thrift 不僅僅是個高效的序列化工具,它是一個完整的 RPC 框架體系!

3.1 堆棧結構

如圖 3.1所示,Thrift 包含一個完整的堆棧結構用于構建客戶端和服務器端。

圖 3.1


其中代碼框架層是根據 Thrift 定義的服務接口描述文件生成的客戶端和服務器端代碼框架,數據讀寫操作層是根據 Thrift 文件生成代碼實現數據的讀寫操作。

3.2 client/server調用流程

首先來看下 Thrift 服務端是如何啟動并提供服務的,如下圖 3.2所示(點擊此處看大圖):

圖 3.2


上圖所示是 HelloServiceServer 啟動的過程,以及服務被客戶端調用時服務器的響應過程。我們可以看到,程序調用了 TThreadPoolServer 的 serve() 方法后,server 進入阻塞監聽狀態,其阻塞在 TServerSocket 的 accept()方法上。當接收到來自客戶端的消息后,服務器發起一個新線程處理這個消息請求,原線程再次進入阻塞狀態。在新線程中,服務器通過 TBinaryProtocol 協議讀取消息內容,調用 HelloServiceImpl 的 helloVoid() 方法,并將結果寫入 helloVoid_result 中傳回客戶端。

在服務啟動后,客戶端就開始調用其服務,如圖 3.3所示(點擊此處看大圖):

圖 3.3


上圖展示的是 HelloServiceClient 調用服務的過程,以及接收到服務器端的返回值后處理結果的過程。我們可以看到,程序調用了 Hello.Client 的 helloVoid() 方法,在 helloVoid() 方法中,通過 send_helloVoid() 方法發送對服務的調用請求,通過 recv_helloVoid() 方法接收服務處理請求后返回的結果。

3.3 數據類型

上一節我們已經大致了解了 Thrift 的 server 和 client 的工作流程,現在就來講講 Thrift 可定義的數據類型。Thrift 支持幾大類數據結構:基本類型、結構體和異常類型、容器類型、服務類型。

基本類型:

bool:布爾值 (true or false), one byte

byte:有符號字節

i16:16位有符號整型

i32:32位有符號整型

i64:64位有符號整型

double:64位浮點型

string:未知編碼或者二進制的字符串

結構體和異常類型:

Thrift 結構體 (struct) 在概念上類似于 C 語言結構體類型,在 java 中 Thrift 結構體將會被轉換成面向對象語言的類。struct 的定義如下:

struct UserDemo {

  1: i32 id;

  2: string name;

  3: i32 age = 25;

  4: string phone;

}

struct 具有以下特性:

struct 不能繼承,但是可以嵌套,不能嵌套自己

其成員都是有明確類型

成員是被正整數編號過的,其中的編號使不能重復的,這個是為了在傳輸過程中編碼使用(詳情往下看備注1)

成員分割符可以是逗號(,)或是分號(;),而且可以混用,但是為了清晰期間,建議在定義中只使用一種,比如java學習者可以就使用逗號(;)

字段會有optional和required之分(詳情往下看備注2)

每個字段可以設置默認值

同一文件可以定義多個struct,也可以定義在不同的文件,進行include引入

備注1:數字標簽作用非常大,隨著項目開發的不斷發展,也許字段會有變化,但是建議不要輕易修改這些數字標簽,修改之后如果沒有同步客戶端和服務器端會讓一方解析出問題。

備注2:關于 struct 字段類型,規范的 struct 定義中的每個域均會使用 required 或者 optional 關鍵字進行標識,但是如果不指定則為無類型,可以不填充該值,但是在序列化傳輸的時候也會序列化進去。其中 optional 是不填充則不序列化,required 是必須填充也必須序列化。如果 required 標識的域沒有賦值,Thrift 將給予提示;如果 optional 標識的域沒有賦值,該域將不會被序列化傳輸;如果某個 optional 標識域有缺省值而用戶沒有重新賦值,則該域的值一直為缺省值;如果某個 optional 標識域有缺省值或者用戶已經重新賦值,而不設置它的 __isset 為 true,也不會被序列化傳輸。

異常在語法和功能上相當于結構體,差別是異常使用關鍵字 exception 而不是 struct 聲明。它在語義上不同于結構體:當定義一個 RPC 服務時,開發者可能需要聲明一個遠程方法拋出一個異常。

容器類型

Thrift 容器與目前流行編程語言的容器類型相對應,有3種可用容器類型:

list:元素類型為t的有序表,容許元素重復。對應java的ArrayList

set:元素類型為t的無序表,不容許元素重復。對應java的HashSet

map:鍵類型為t,值類型為t的kv對,鍵不容許重復。對對應Java的HashMap

其中容器中元素類型可以是除了 service 外的任何合法 Thrift 類型(包括結構體和異常)。

服務類型

服務的定義方法在語義上等同于面向對象語言中的接口。Thrift 編譯器會產生執行這些接口的 client 和 server 存根(詳情下一節會具體描述)。下面我們就舉個簡單的例子解釋 service 如何定義:

service QuerySrv{

  /**

  * 本方法實現根據名字和年齡來找到對應的用戶信息

  */

  UserDemo qryUser(1:string name, 2:i32 age);


  /**

  * 本方法實現根據id找到對應用戶的手機號碼

  */

  string queryPhone(1:i32 id);

}

在上面的例子中我們定義了一個 service 類型的結構,里面包含兩個方法的定義。

在定義 services 的時候,我們還需要了解一下規則:

繼承類必須實現這些方法

參數可以是基本類型或者結構體

所有的參數都是const類型,不能作為返回值

返回值可以是void(oneway的返回值一定是void)

服務支持繼承,一個service可使用extends關鍵字繼承另一個service

服務不支持重載

除上面所提到的四大數據類型外,Thrift 還支持枚舉類型(enum)和常量類型(const)。

命名空間

Thrift 中的命名空間類似于 java 中的 package,它們提供了一種組織(隔離)代碼的簡便方式。名字空間也可以用于解決類型定義中的名字沖突。

3.4 傳輸體系

傳輸協議

Thrift 支持多種傳輸協議,用戶可以根據實際需求選擇合適的類型。Thrift 傳輸協議上總體可劃分為文本 (text) 和二進制 (binary) 傳輸協議兩大類,一般在生產環境中使用二進制類型的傳輸協議為多數(相對于文本和 JSON 具有更高的傳輸效率)。常用的協議包含:

TBinaryProtocol:是Thrift的默認協議,使用二進制編碼格式進行數據傳輸,基本上直接發送原始數據

TCompactProtocol:壓縮的、密集的數據傳輸協議,基于Variable-length quantity的zigzag 編碼格式

TJSONProtocol:以JSON (JavaScript Object Notation)數據編碼協議進行數據傳輸

TDebugProtocol:常常用以編碼人員測試,以文本的形式展現方便閱讀

關于以上幾種類型的傳輸協議,如果想更深入更具體的了解其實現及工作原理,可以參考站外相關文章《thrift源碼研究》。

傳輸方式

與傳輸協議一樣,Thrift 也支持幾種不同的傳輸方式。

1. TSocket:阻塞型 socket,用于客戶端,采用系統函數 read 和 write 進行讀寫數據。

2. TServerSocket:非阻塞型 socket,用于服務器端,accecpt 到的 socket 類型都是 TSocket(即阻塞型 socket)。

3. TBufferedTransportTFramedTransport都是有緩存的,均繼承TBufferBase,調用下一層 TTransport 類進行讀寫操作嗎,結構極為相似。其中 TFramedTransport 以幀為傳輸單位,幀結構為:4個字節(int32_t)+傳輸字節串,頭4個字節是存儲后面字節串的長度,該字節串才是正確需要傳輸的數據,因此 TFramedTransport 每傳一幀要比 TBufferedTransport 和 TSocket 多傳4個字節。

4. TMemoryBuffer繼承 TBufferBase,用于程序內部通信用,不涉及任何網絡I/O,可用于三種模式:(1)OBSERVE模式,不可寫數據到緩存;(2)TAKE_OWNERSHIP模式,需負責釋放緩存;(3)COPY模式,拷貝外面的內存塊到TMemoryBuffer。

5. TFileTransport直接繼承 TTransport,用于寫數據到文件。對事件的形式寫數據,主線程負責將事件入列,寫線程將事件入列,并將事件里的數據寫入磁盤。這里面用到了兩個隊列,類型為 TFileTransportBuffer,一個用于主線程寫事件,另一個用于寫線程讀事件,這就避免了線程競爭。在讀完隊列事件后,就會進行隊列交換,由于由兩個指針指向這兩個隊列,交換只要交換指針即可。它還支持以 chunk(塊)的形式寫數據到文件。

6. TFDTransport是非常簡單地寫數據到文件和從文件讀數據,它的 write 和 read 函數都是直接調用系統函數 write 和 read 進行寫和讀文件。

7. TSimpleFileTransport直接繼承 TFDTransport,沒有添加任何成員函數和成員變量,不同的是構造函數的參數和在 TSimpleFileTransport 構造函數里對父類進行了初始化(打開指定文件并將fd傳給父類和設置父類的close_policy為CLOSE_ON_DESTROY)。

8. TZlibTransport跟 TBufferedTransport 和 TFramedTransport一樣,調用下一層 TTransport 類進行讀寫操作。它采用提供的 zlib 壓縮和解壓縮庫函數來進行壓解縮,寫時先壓縮再調用底層 TTransport 類發送數據,讀時先調用 TTransport 類接收數據再進行解壓,最后供上層處理。

9. TSSLSocket繼承 TSocket,阻塞型 socket,用于客戶端。采用 openssl 的接口進行讀寫數據。checkHandshake()函數調用 SSL_set_fd 將 fd 和 ssl 綁定在一起,之后就可以通過 ssl 的 SSL_read和SSL_write 接口進行讀寫網絡數據。

10. TSSLServerSocket繼承 TServerSocket,非阻塞型 socket, 用于服務器端。accecpt 到的 socket 類型都是 TSSLSocket 類型。

11. THttpClientTHttpServer是基于 Http1.1 協議的繼承 Transport 類型,均繼承 THttpTransport,其中 THttpClient 用于客戶端,THttpServer 用于服務器端。兩者都調用下一層 TTransport 類進行讀寫操作,均用到TMemoryBuffer 作為讀寫緩存,只有調用 flush() 函數才會將真正調用網絡 I/O 接口發送數據。

TTransport 是所有 Transport 類的父類,為上層提供了統一的接口而且通過 TTransport 即可訪問各個子類不同實現,類似多態。

四. 選擇 java server 的藝術

Thrift 包含三個主要的組件:protocol,transport 和 server。

其中,protocol 定義了消息是怎樣序列化的;transport 定義了消息是怎樣在客戶端和服務器端之間通信的;server 用于從 transport 接收序列化的消息,根據 protocol 反序列化之,調用用戶定義的消息處理器,并序列化消息處理器的響應,然后再將它們寫回 transport。

Thrift 模塊化的結構使得它能提供各種 server 實現。下面列出了 Java 中可用的 server 實現:

TSimpleServer

TNonblockingServer

THsHaServer

TThreadedSelectorServer

TThreadPoolServer

有多個選擇固然是很好的,但如果不清楚個中差別則是個災難。所以接下來就談談這些 server 之間的區別,并通過一些簡單的測試以說明它們的性能特點。

TSimpleServer

TSimplerServer 接受一個連接,處理連接請求,直到客戶端關閉了連接,它才回去接受一個新的連接。正因為它只在一個單獨的線程中以阻塞 I/O 的方式完成這些工作,所以它只能服務一個客戶端連接,其他所有客戶端在被服務器端接受之前都只能等待。

TSimpleServer 主要用于測試目的,不要在生產環境中使用它!

TNonblockingServer vs. THsHaServer

TNonblockingServer 使用非阻塞的 I/O 解決了 TSimpleServer 一個客戶端阻塞其他所有客戶端的問題。它使用了 java.nio.channels.Selector,通過調用 select(),它使得你阻塞在多個連接上,而不是阻塞在單一的連接上。當一或多個連接準備好被接受/讀/寫時,select() 調用便會返回。TNonblockingServer 處理這些連接的時候,要么接受它,要么從它那讀數據,要么把數據寫到它那里,然后再次調用 select() 來等待下一個可用的連接。通用這種方式,server 可同時服務多個客戶端,而不會出現一個客戶端把其他客戶端全部“餓死”的情況。

然而,還有個棘手的問題:所有消息是被調用 select() 方法的同一個線程處理的。假設有10個客戶端,處理每條消息所需時間為100毫秒,那么,latency 和吞吐量分別是多少?當一條消息被處理的時候,其他9個客戶端就等著被 select,所以客戶端需要等待1秒鐘才能從服務器端得到回應,吞吐量就是10個請求/秒。如果可以同時處理多條消息的話,會很不錯吧?

因此,THsHaServer(半同步/半異步的 server)就應運而生了。它使用一個單獨的線程來處理網絡I/O,一個獨立的 worker 線程池來處理消息。這樣,只要有空閑的 worker 線程,消息就會被立即處理,因此多條消息能被并行處理。用上面的例子來說,現在的 latency 就是100毫秒,而吞吐量就是100個請求/秒。

為了演示做了一個測試,有10客戶端和一個修改過的消息處理器——它的功能僅僅是在返回之前簡單地 sleep 100 毫秒。使用的是有10個 worker 線程的 THsHaServer。消息處理器的代碼看上去就像下面這樣:

publicResponseCode sleep() throwsTException{?

????try{

????????Thread.sleep(100);

????} catch(Exception ex) {

????}

????returnResponseCode.Success;

}

特別申明,本章節的測試結果摘自站外文章,詳情請看文末鏈接

圖 4.1


圖 4.2


結果正如我們想像的那樣,THsHaServer 能夠并行處理所有請求,而 TNonblockingServer 只能一次處理一個請求。

THsHaServer vs. TThreadedSelectorServer

Thrift 0.8 引入了另一種 server 實現,即 TThreadedSelectorServer。它與 THsHaServer 的主要區別在于,TThreadedSelectorServer 允許你用多個線程來處理網絡 I/O。它維護了兩個線程池,一個用來處理網絡 I/O,另一個用來進行請求的處理。當網絡 I/O 是瓶頸的時候,TThreadedSelectorServer 比 THsHaServer 的表現要好。為了展現它們的區別進行一個測試,令其消息處理器在不做任何工作的情況下立即返回,以衡量在不同客戶端數量的情況下的平均 latency 和吞吐量。對 THsHaServer,使用32個 worker 線程;對 TThreadedSelectorServer,使用16個 worker 線程和16個 selector 線程。

圖 4.3


圖 4.4


結果顯示,TThreadedSelectorServer 比 THsHaServer 的吞吐量高得多,并且維持在一個更低的 latency 上。

TThreadedSelectorServer vs. TThreadPoolServer

最后,還剩下 TThreadPoolServer。TThreadPoolServer 與其他三種 server 不同的是:

有一個專用的線程用來接受連接

一旦接受了一個連接,它就會被放入 ThreadPoolExecutor 中的一個 worker 線程里處理。

worker 線程被綁定到特定的客戶端連接上,直到它關閉。一旦連接關閉,該 worker 線程就又回到了線程池中。

你可以配置線程池的最小、最大線程數,默認值分別是5(最小)和 Integer.MAX_VALUE(最大)。

這意味著,如果有1萬個并發的客戶端連接,你就需要運行1萬個線程。所以它對系統資源的消耗不像其他類型的 server 一樣那么“友好”。此外,如果客戶端數量超過了線程池中的最大線程數,在有一個 worker 線程可用之前,請求將被一直阻塞在那里。

我們已經說過,TThreadPoolServer 的表現非常優異。在我正在使用的計算機上,它可以支持1萬個并發連接而沒有任何問題。如果你提前知道了將要連接到你服務器上的客戶端數量,并且你不介意運行大量線程的話,TThreadPoolServer 對你可能是個很好的選擇。

圖 4.5


圖 4.6


我想你可以從上面的描述可以幫你做出決定:哪一種 Thrift server 適合你。

TThreadedSelectorServer 對大多數案例來說都是個安全之選。如果你的系統資源允許運行大量并發線程的話,建議你使用 TThreadPoolServer。

五. Let’s do it

上面已經介紹了很多理論知識了,很多同學還是不知道如何使用呢!好吧,是時候表演真正的技術了(LOL…)。

所謂大道至簡,講的就是最簡單的代碼就是最優美的代碼,只要功能強悍,最簡單的代碼也掩蓋不了它出眾的氣質。下面就來給大伙兒講講如何使用 Thrift 強大的代碼生成引擎來生成 java 代碼,并通過詳細的步驟實現 Thrift Server 和 Client 調用。

備注:本文實現基于 Thrift-0.9.2 版本,實現過程忽略日志處理等非關鍵代碼。

步驟一:首先從官網中下載對應的 Window 平臺編譯器(點擊下載 thrift-0.9.2.exe)。使用 IDL 描述語言建立 .thrift 文件。本文提供一個實現簡單功能的測試案例,如下所示:

/**

* 文件名為TestQry.thrift

* 實現功能:創建一個查詢結果struct和一個服務接口service

* 基于:thrift-0.9.2

**/

namespace java com.thrift

struct QryResult {

????????/**

????????*返回碼, 1成功,0失敗

????????*/

????????1:i32 code;

????????/**

????????*響應信息

????????*/

????????2:string msg;

}

service TestQry{

????????/**

????????* 測試查詢接口,當qryCode值為1時返回"成功"的響應信息,qryCode值為其他值時返回"失敗"的響應信息

????????* @param qryCode測試參數

????????*/

????????QryResult qryTest(1:i32 qryCode)

}

步驟二:將上述 TestQry.thrift 文件與 thrift-0.9.2.exe 放在同一目錄,如下:

圖 5.1


在命令提示符 CMD 中進入文件目錄所在目錄,執行代碼生成命令:

1thrift-0.9.2.exe -r -gen java TestQry.thrift

執行之后,我們在文件夾中可以看到生成的 java 代碼

圖 5.2


步驟三:接下來我們新建 Maven Project(注意:JDK 版本1.5及以上),將上一步驟生成的代碼拷貝到項目,并在 pom.xml 中加載 Thrift 的依賴,如下

    org.apache.thrift

    libthrift

    0.9.2


    org.slf4j

    slf4j-api

    1.7.13

步驟四:創建 QueryImp.java 實現 TestQry.Iface 接口,關鍵代碼如下

publicclassQueryImp implementsTestQry.Iface{

  @Override

  publicQryResult qryTest(intqryCode) throwsTException {

    QryResult result = newQryResult();

    if(qryCode==1){

      result.code = 1;

      result.msg = "success";

    }else{

      result.code = 0;

      result.msg = "fail";

    }

    returnresult;

  }

}

步驟五:創建 ThriftServerDemo.java 實現服務端(本例采用非阻塞I/O,二進制傳輸協議),關鍵代碼如下

publicclassThriftServerDemo {

  privatefinalstaticintDEFAULT_PORT = 30001;

  privatestaticTServer server = null;

  publicstaticvoidmain(String[] args){

    try{

      TNonblockingServerSocket socket = newTNonblockingServerSocket(DEFAULT_PORT);

      TestQry.Processor processor = newTestQry.Processor(newQueryImp());

      TNonblockingServer.Args arg = newTNonblockingServer.Args(socket);

      arg.protocolFactory(newTBinaryProtocol.Factory());

      arg.transportFactory(newTFramedTransport.Factory());

      arg.processorFactory(newTProcessorFactory(processor));

      server = newTNonblockingServer (arg);

      server.serve();

    } catch(TTransportException e) {

      e.printStackTrace();

    }

  }

}

步驟六:創建 ThriftClientDemo.java 實現客戶端,關鍵代碼如下

publicclassThriftClientDemo {

  privatefinalstaticintDEFAULT_QRY_CODE = 1;

  publicstaticvoidmain(String[] args){

    try{

      TTransport tTransport = getTTransport();

      TProtocol protocol = newTBinaryProtocol(tTransport);

      TestQry.Client client = newTestQry.Client(protocol);

      QryResult result = client.qryTest(DEFAULT_QRY_CODE);

      System.out.println("code="+result.code+" msg="+result.msg);

    }catch(Exception e) {

      e.printStackTrace();

    }

  }

  privatestaticTTransport getTTransport() throwsException{

    try{

      TTransport tTransport = getTTransport("127.0.0.1", 30001, 5000);

      if(!tTransport.isOpen()){

        tTransport.open();

      }

      returntTransport;

    }catch(Exception e){

      e.printStackTrace();

    }

    returnnull;

  }

  privatestaticTTransport getTTransport(String host, intport, inttimeout) {

    finalTSocket tSocket = newTSocket(host, port, timeout);

    finalTTransport transport = newTFramedTransport(tSocket);

    returntransport;

  }

}

好的,所有準備工作都已經做好了,接下來我們就來進行 Client 和 Server 的通信。先運行 ThriftServerDemo 啟動 Server,然后運行 ThriftClientDemo.java 創建 Client 進行調用,當 qryCode = 1 時,結果如下

1code=1msg=success

當 qryCode = 0 時,結果如下

1code=0msg=fail

附上項目的代碼結構:

圖 5.3


你看我沒騙你吧,是不是 so easy ?

當然在項目中使用時絕對沒有這么簡單,但上面的栗子已經足夠用來指導你進行 Thrift 服務端和客戶端開發了。

六. 路漫漫其修遠兮

到目前為止你所看到的都不是源碼分析層面的知識,本文的目的也并非在此。掌握任何一門技術,都應該先從其宏觀體系和架構開始了解,然后再去深入研究其中的細節和精髓。如果一開始就追求所謂的源碼解析等“高大上”的東西,反而會因為擁有了一顆大樹而失去了欣賞整個森林的美妙。

最后喜歡小編的可以關注一下的啦,小編希望可以跟大家探討交流一下的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容