1、Thrift 框架介紹javascript
一、前言php
Thrift是一個跨語言的服務部署框架,最初由Facebook於2007年開發,2008年進入Apache開源項目。Thrift經過一箇中間語言(IDL, 接口定義語言)來定義RPC的接口和數據類型,而後經過一個編譯器生成不一樣語言的代碼(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),並由生成的代碼負責RPC協議層和傳輸層的實現。html
本文組織結構以下:1)引言 2)架構3)支持的數據傳輸格式、數據傳輸方式和服務模型 4)Thrift安裝 5)利用Thift部署服務java
二、架構python
Thrift其實是實現了C/S模式,經過代碼生成工具將接口定義文件生成服務器端和客戶端代碼(能夠爲不一樣語言),從而實現服務端和客戶端跨語言的支持。用戶在Thirft描述文件中聲明本身的服務,這些服務通過編譯後會生成相應語言的代碼文件,而後用戶實現服務(客戶端調用服務,服務器端提服務)即可以了。其中protocol(協議層, 定義數據傳輸格式,能夠爲二進制或者XML等)和transport(傳輸層,定義數據傳輸方式,能夠爲TCP/IP傳輸,內存共享或者文件共享等)被用做運行時庫。上圖的詳細解釋參考引用【1】。linux
三、 支持的數據傳輸格式、數據傳輸方式和服務模型git
(1)支持的傳輸格式github
TBinaryProtocol – 二進制格式.web
TCompactProtocol – 壓縮格式shell
TJSONProtocol – JSON格式
TSimpleJSONProtocol –提供JSON只寫協議, 生成的文件很容易經過腳本語言解析。
TDebugProtocol – 使用易懂的可讀的文本格式,以便於debug
(2) 支持的數據傳輸方式
TSocket -阻塞式socker
TFramedTransport – 以frame爲單位進行傳輸,非阻塞式服務中使用。
TFileTransport – 以文件形式進行傳輸。
TMemoryTransport – 將內存用於I/O. java實現時內部實際使用了簡單的ByteArrayOutputStream。
TZlibTransport – 使用zlib進行壓縮, 與其餘傳輸方式聯合使用。當前無java實現。
(3)支持的服務模型
TSimpleServer – 簡單的單線程服務模型,經常使用於測試
TThreadPoolServer – 多線程服務模型,使用標準的阻塞式IO。
TNonblockingServer – 多線程服務模型,使用非阻塞式IO(需使用TFramedTransport數據傳輸方式)
四、 Thrift安裝
下載:http://archive.apache.org/dist/thrift/
安裝要求:
Unix/linux 系統,windows+cygwin
C++語言:g++、boost
java 語言:JDK、Apache Ant
其餘語言:Python、PHP、Perl, etc…
編譯安裝:./configure –》make –》make install
一、 利用Thrift部署服務
主要流程:編寫服務說明,保存到.thrift文件–》根據須要, 編譯.thrift文件,生成相應的語言源代碼–》根據實際須要, 編寫client端和server端代碼。
(1).thrift文件編寫
通常將服務放到一個.thrift文件中,服務的編寫語法與C語言語法基本一致,在.thrift文件中有主要有如下幾個內容:變量聲明、數據聲明(struct)和服務接口聲明(service, 能夠繼承其餘接口)。
下面分析Thrift的tutorial中帶的例子tutorial.thrift
包含頭文件:
59行:include 「shared.thrift」
–
指定目標語言:
65行:namespace cpp tutorial
–
定義變量:
80行:const i32 INT32CONSTANT = 9853
–
定義結構體:
103行:struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
—
定義服務:
service Calculator extends shared.SharedService {
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
oneway void zip()
}
要生成C++代碼:./thrift --gen cpp tutorial.thrift,結果代碼存放在gen-cpp目錄下
要生成java代碼:./thrift --gen java tutorial.thrift,結果代碼存放在gen-java目錄下 …..
(2) client端和server端代碼編寫
client端和sever端代碼要調用編譯.thrift生成的中間文件。
下面分析cpp文件下面的CppClient.cpp和CppServer.cpp代碼
在client端,用戶自定義CalculatorClient類型的對象(用戶在.thrift文件中聲明的服務名稱是Calculator, 則生成的中間代碼中的主類爲CalculatorClient), 該對象中封裝了各類服務,能夠直接調用(如client.ping()), 而後thrift會經過封裝的rpc調用server端同名的函數。
在server端,須要實如今.thrift文件中聲明的服務中的全部功能,以便處理client發過來的請求。
【參考資料】
一、 http://wiki.apache.org/thrift/
二、 http://jnb.ociweb.com/jnb/jnbJun2009.html
三、 http://blog.rushcj.com/tag/thrift/
四、 http://www.vvcha.cn/c.aspx?id=31984
五、 http://www.thoss.org.cn/mediawiki/index.php/Thrift的通訊機制及其在cassandra中的應用
原創文章,轉載請註明: 轉載自董的博客
本文連接地址: http://dongxicheng.org/search-engine/thrift-framework-intro/
2、Thrift 使用指南
1. 內容概要
本文檔比較全面的介紹了thrift語法,代碼生成結構和應用經驗。本文主要講述的對象是thrift文件,並未涉及其client和server的編寫方法。
本文檔大部份內容翻譯自文章:「Thrift:The missing Guide「。
2. 語法參考
2.1 Types
Thrift類型系統包括預約義基本類型,用戶自定義結構體,容器類型,異常和服務定義
(1) 基本類型
bool
:布爾類型(
true
or value),佔一個字節
byte:有符號字節
i16:16位有符號整型
i32:32位有符號整型
i64:64位有符號整型
double
:64位浮點數
string:未知編碼或者二進制的字符串
注意,thrift不支持無符號整型,由於不少目標語言不存在無符號整型(如java)。
(2) 容器類型
Thrift容器與類型密切相關,它與當前流行編程語言提供的容器類型相對應,採用java泛型風格表示的。Thrift提供了3種容器類型:
List<t1>:一系列t1類型的元素組成的有序表,元素能夠重複
Set<t1>:一系列t1類型的元素組成的無序表,元素惟一
Map<t1,t2>:key/value對(key的類型是t1且key惟一,value類型是t2)。
容器中的元素類型能夠是除了service意外的任何合法thrift類型(包括結構體和異常)。
(3) 結構體和異常
Thrift結構體在概念上同C語言結構體類型—-一種將相關屬性彙集(封裝)在一塊兒的方式。在面嚮對象語言中,thrift結構體被轉換成類。
異常在語法和功能上相似於結構體,只不過異常使用關鍵字exception而不是struct關鍵字聲明。但它在語義上不一樣於結構體—當定義一個RPC服務時,開發者可能須要聲明一個遠程方法拋出一個異常。
結構體和異常的聲明將在下一節介紹。
(4) 服務
服務的定義方法在語法上等同於面嚮對象語言中定義接口。Thrift編譯器會產生實現這些接口的client和server樁。具體參見下一節。
(5) 類型定義
Thrift支持C/C++風格的typedef:
typedef
i32 MyInteger \\a
typedef
Tweet ReTweet \\b
說明:
a. 末尾沒有逗號
b. struct可使用typedef
2.2 枚舉類型
能夠像C/C++那樣定義枚舉類型,如:
enum
TweetType {
TWEET,
//a
RETWEET = 2,
//b
DM = 0xa,
//c
REPLY
}
//d
struct
Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc;
5: optional TweetType tweetType = TweetType.TWEET
// e
16: optional string language =
"english"
}
說明:
a. 編譯器默認從0開始賦值
b. 能夠賦予某個常量某個整數
c. 容許常量是十六進制整數
d. 末尾沒有逗號
e. 給常量賦缺省值時,使用常量的全稱
注意,不一樣於protocol buffer,thrift不支持枚舉類嵌套,枚舉常量必須是32位的正整數
2.3 註釋
Thrfit支持shell註釋風格,C/C++語言中單行或者多行註釋風格
# This is a valid comment.
// C++/Java style single-line comments work just as well.
2.4 命名空間
Thrift中的命名空間同C++中的namespace和java中的package相似,它們均提供了一種組織(隔離)代碼的方式。由於每種語言均有本身的命名空間定義方式(如python中有module),thrift容許開發者針對特定語言定義namespace:
namespace
cpp com.example.project
// a
namespace
java com.example.project
// b
說明:
a. 轉化成namespace com { namespace example { namespace project {
b. 轉換成package com.example.project
2.5 文件包含
Thrift容許thrift文件包含,用戶須要使用thrift文件名做爲前綴訪問被包含的對象,如:
include
"tweet.thrift"
// a
...
struct
TweetSearchResult {
1: list<tweet.Tweet> tweets;
// b
}
說明:
a. thrift文件名要用雙引號包含,末尾沒有逗號或者分號
b. 注意tweet前綴
2.6 常量
Thrift容許用戶定義常量,複雜的類型和結構體可以使用JSON形式表示。
const
i32 INT_CONST = 1234;
// a
const
map<string,string> MAP_CONST = {
"hello"
:
"world"
,
"goodnight"
:
"moon"
}
說明:
a. 分號是可選的,無關緊要;支持十六進制賦值。
2.7 定義結構體
結構體由一系列域組成,每一個域有惟一整數標識符,類型,名字和可選的缺省參數組成。如:
struct
Tweet {
1: required i32 userId;
// a
2: required string userName;
// b
3: required string text;
4: optional Location loc;
// c
16: optional string language =
"english"
// d
}
struct
Location {
// e
1: required
double
latitude;
2: required
double
longitude;
}
說明:
a. 每一個域有一個惟一的,正整數標識符
b. 每一個域能夠標識爲required或者optional(也能夠不註明)
c. 結構體能夠包含其餘結構體
d. 域能夠有缺省值
e. 一個thrift中可定義多個結構體,並存在引用關係
規範的struct定義中的每一個域均會使用required或者optional關鍵字進行標識。若是required標識的域沒有賦值,thrift將給予提示。若是optional標識的域沒有賦值,該域將不會被序列化傳輸。若是某個optional標識域有缺省值而用戶沒有從新賦值,則該域的值一直爲缺省值。
與service不一樣,結構體不支持繼承,即,一個結構體不能繼承另外一個結構體。
2.8 定義服務
在流行的序列化/反序列化框架(如protocol buffer)中,thrift是少有的提供多語言間RPC服務的框架。
Thrift編譯器會根據選擇的目標語言爲server產生服務接口代碼,爲client產生樁代碼。
//「Twitter」與「{」之間須要有空格!!!
service Twitter {
// 方法定義方式相似於C語言中的方式,它有一個返回值,一系列參數和可選的異常
// 列表. 注意,參數列表和異常列表定義方式與結構體中域定義方式一致.
void
ping(),
// a
bool
postTweet(1:Tweet tweet);
// b
TweetSearchResult searchTweets(1:string query);
// c
// 」oneway」標識符表示client發出請求後沒必要等待回覆(非阻塞)直接進行下面的操做,
// 」oneway」方法的返回值必須是void
oneway
void
zip()
// d
}
說明:
a. 函數定義可使用逗號或者分號標識結束
b. 參數能夠是基本類型或者結構體,參數是隻讀的(const),不能夠做爲返回值!!!
c. 返回值能夠是基本類型或者結構體
d. 返回值能夠是void
注意,函數中參數列表的定義方式與struct徹底同樣
Service支持繼承,一個service可以使用extends關鍵字繼承另外一個service
3. 產生代碼
本節介紹thrift產生各類目標語言代碼的方式。本節從幾個基本概念開始,逐步引導開發者瞭解產生的代碼是怎麼樣組織的,進而幫助開發者更快地明白thrift的使用方法。
概念
Thrift的網絡棧以下所示:
3.1 Transport
Transport層提供了一個簡單的網絡讀寫抽象層。這使得thrift底層的transport從系統其它部分(如:序列化/反序列化)解耦。如下是一些Transport接口提供的方法:
open
close
read
write
flush
除了以上幾個接口,Thrift使用ServerTransport接口接受或者建立原始transport對象。正如名字暗示的那樣,ServerTransport用在server端,爲到來的鏈接建立Transport對象。
open
listen
accept
close
3.2 Protocol
Protocol抽象層定義了一種將內存中數據結構映射成可傳輸格式的機制。換句話說,Protocol定義了datatype怎樣使用底層的Transport對本身進行編解碼。所以,Protocol的實現要給出編碼機制並負責對數據進行序列化。
Protocol接口的定義以下:
writeMessageBegin(name, type, seq)
writeMessageEnd()
writeStructBegin(name)
writeStructEnd()
writeFieldBegin(name, type, id)
writeFieldEnd()
writeFieldStop()
writeMapBegin(ktype, vtype, size)
writeMapEnd()
writeListBegin(etype, size)
writeListEnd()
writeSetBegin(etype, size)
writeSetEnd()
writeBool(
bool
)
writeByte(byte)
writeI16(i16)
writeI32(i32)
writeI64(i64)
writeDouble(
double
)
writeString(string)
name, type, seq = readMessageBegin()
readMessageEnd()
name = readStructBegin()
readStructEnd()
name, type, id = readFieldBegin()
readFieldEnd()
k, v, size = readMapBegin()
readMapEnd()
etype, size = readListBegin()
readListEnd()
etype, size = readSetBegin()
readSetEnd()
bool
= readBool()
byte = readByte()
i16 = readI16()
i32 = readI32()
i64 = readI64()
double
= readDouble()
string = readString()
下面是一些對大部分thrift支持的語言都可用的protocol:
(1) binary:簡單的二進制編碼
(2) Compact:具體見THRIFT-11
(3) Json
3.3 Processor
Processor封裝了從輸入數據流中讀數據和向數據數據流中寫數據的操做。讀寫數據流用Protocol對象表示。Processor的結構體很是簡單:
interface TProcessor {
bool
process(TProtocol in, TProtocol out) throws TException
}
與服務相關的processor實現由編譯器產生。Processor主要工做流程以下:從鏈接中讀取數據(使用輸入protocol),將處理受權給handler(由用戶實現),最後將結果寫到鏈接上(使用輸出protocol)。
3.4 Server
Server將以上全部特性集成在一塊兒:
(1) 建立一個transport對象
(2) 爲transport對象建立輸入輸出protocol
(3) 基於輸入輸出protocol建立processor
(4) 等待鏈接請求並將之交給processor處理
3.5 應用舉例
下面,咱們討論thrift文件產生的特定語言代碼。下面給出thrift文件描述:
namespace
cpp thrift.example
namespace
java thrift.example
enum
TweetType {
TWEET,
RETWEET = 2,
DM = 0xa,
REPLY
}
struct
Location {
1: required
double
latitude;
2: required
double
longitude;
}
struct
Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc;
5: optional TweetType tweetType = TweetType.TWEET;
16: optional string language =
"english"
;
}
typedef
list<Tweet> TweetList
struct
TweetSearchResult {
1: TweetList tweets;
}
const
i32 MAX_RESULTS = 100;
service Twitter {
void
ping(),
bool
postTweet(1:Tweet tweet);
TweetSearchResult searchTweets(1:string query);
oneway
void
zip()
}
(1) Java語言
(a) 產生的文件
一個單獨的文件(Constants.java)包含全部的常量定義。
每一個結構體,枚舉或者服務各佔一個文件
$ tree gen-java
`– thrift
`– example
|– Constants.java
|– Location.java
|– Tweet.java
|– TweetSearchResult.java
|– TweetType.java
`– Twitter.java
(b) 類型
thrift將各類基本類型和容器類型映射成java類型:
bool
: boolean
byte: byte
i16:
short
i32:
int
i64:
long
double
:
double
string: String
list<t1>: List<t1>
set<t1>: Set<t1>
map<t1,t2>: Map<t1, t2>
(c) typedef
Java不支持typedef,它只使用原始類型,如,在上面的例子中,產生的代碼中,TweetSearchResult會被還原成list<Tweet> tweets
(d) Enum
Thrift直接將枚舉類型映射成java的枚舉類型。用戶可使用geValue方法獲取枚舉常量的值。此外,編譯器會產生一個findByValue方法獲取枚舉對應的數值。
(e) 常量
Thrift把全部的常量放在一個叫Constants的public類中,每一個常量修飾符是public static final。
(2) C++語言
(a) 產生的文件
全部變量均存放在一個.cpp/.h文件對中
全部的類型定義(枚舉或者結構體)存放到另外一個.cpp/.h文件對中
每個service有本身的.cpp/.h文件
$ tree gen-cpp
|– example_constants.cpp
|– example_constants.h
|– example_types.cpp
|– example_types.h
|– Twitter.cpp
|– Twitter.h
`– Twitter_server.skeleton.cpp
其餘語言
Python,Ruby,javascript等
4. 實踐經驗
thrift文件內容可能會隨着時間變化的。若是已經存在的消息類型再也不符合設計要求,好比,新的設計要在message格式中添加一個額外字段,但你仍想使用之前的thrift文件產生的處理代碼。若是想要達到這個目的,只需:
(1) 不要修改已存在域的整數編號
(2) 新添加的域必須是optional的,以便格式兼容。對於一些語言,若是要爲optional的字段賦值,須要特殊處理,好比對於C++語言,要爲
struct
Example{
1 : i32 id,
2 : string name,
3 : optional age,
}
中的optional字段age賦值,須要將它的__isset值設爲true,這樣才能序列化並傳輸或者存儲(否則optional字段被認爲不存在,不會被傳輸或者存儲),
如:
Example example;
......
example.age=10,
example.__isset.age =
true
;
//__isset是每一個thrift對象的自帶的public成員,來指定optional字段是否啓用並賦值。
......
(3) 非required域能夠刪除,前提是它的整數編號不會被其餘域使用。對於刪除的字段,名字前面可添加「OBSOLETE_」以防止其餘字段使用它的整數編號。
(4) thrift文件應該是unix格式的(windows下的換行符與unix不一樣,可能會致使你的程序編譯不過),若是是在window下編寫的,可以使用dos2unix轉化爲unix格式。
(5) 貌似當前的thrift版本(0.6.1)不支持常量表達式的定義(如 const i32 DAY = 24 * 60 * 60),這多是考慮到不一樣語言,運算符不盡相同。
原創文章,轉載請註明: 轉載自董的博客
本文連接地址: http://dongxicheng.org/search-engine/thrift-guide/
做者:Dong,做者介紹:http://dongxicheng.org/about/
3、使用Thrift RPC編寫程序
1. 概述
本文以C++語言爲例介紹了thrift RPC的使用方法,包括對象序列化和反序列化,數據傳輸和信息交換等。
本文采用了一個示例進行說明,該示例主要完成傳輸(上報日誌或者報表)功能,該示例會貫穿本文,內容涉及thrift定義,代碼生成,thrift類說明,client編寫方法,server編寫方法等。
2. 示例描述
假設咱們要使用thrift RPC完成一個數據傳輸任務,數據格式和PRC接口用一個thrift文件描述,具體以下:
(1) book.thrift,用於描述書籍信息的thrift接口
//book.thrift,
namespace
cpp example
struct
Book_Info {
1: i32 book_id,
2: string book_name,
3: string book_author,
4:
double
book_price,
5: string book_publisher,
}
(2) rpc.thrift,client向server傳輸數據(上報日誌或者報表)的RPC接口
//rpc.thrift
namespace
cpp example
include
"book.thrift"
service BookServlet {
bool
Sender(1: list<book.Book_Info> books);
oneway
void
Sender2(1: list<book.Book_Info> books);
}
說明:該thrift文件定義了一個service,它包含兩個接口,server端須要實現這兩個接口以對client提供服務。其中,第一個接口函數是阻塞式的,即要等待server返回值之後才能繼續,另一個聲明爲oneway類型(返回值爲void),代表該函數是非阻塞式的,將數據發給 server後沒必要等待返回結果,但使用該函數時,須要考慮server的承受能力,適度的調整發送頻率。
3. Thrift文件與生成的代碼對應關係
每一個thrift文件會產生四個文件,分別爲:${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
對於本文中的例子,會產生如下文件:
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:併發和時鐘管理方面的庫
processor:Processor相關類
protocal:Protocal相關類
transport:transport相關類
server:server相關類
4.1 Transport類(how is transmitted?)
負責數據傳輸,有如下幾個可用類:
TFileTransport:文件(日誌)傳輸類,容許client將文件傳給server,容許server將收到的數據寫到文件中。
THttpTransport:採用Http傳輸協議進行數據傳輸
TSocket:採用TCP Socket進行數據傳輸
TZlibTransport:壓縮後對數據進行傳輸,或者將收到的數據解壓
下面幾個類主要是對上面幾個類地裝飾(採用了裝飾模式),以提升傳輸效率。
TBufferedTransport:對某個Transport對象操做的數據進行buffer,即從buffer中讀取數據進行傳輸,或者將數據直接寫入buffer
TFramedTransport:同TBufferedTransport相似,也會對相關數據進行buffer,同時,它支持定長數據發送和接收。
TMemoryBuffer:從一個緩衝區中讀寫數據
4.2 Protocol類(what is transmitted?)
負責數據編碼,主要有如下幾個可用類:
TBinaryProtocol:二進制編碼
TJSONProtocol:JSON編碼
TCompactProtocol:密集二進制編碼
TDebugProtocol:以用戶易讀的方式組織數據
4.3 Server類(providing service for clients)
TSimpleServer:簡單的單線程服務器,主要用於測試
TThreadPoolServer:使用標準阻塞式IO的多線程服務器
TNonblockingServer:使用非阻塞式IO的多線程服務器,TFramedTransport必須使用該類型的server
5. 對象序列化和反序列化
Thrift中的Protocol負責對數據進行編碼,於是可以使用Protocol相關對象進行序列化和反序列化。
因爲對象序列化和反序列化不設計傳輸相關的問題,因此,可以使用TBinaryProtocol和TMemoryBuffer,具體以下:
(1) 使用thrift進行對象序列化
//對對象object進行序列化,保存到str中
template
<
typename
Type>
void
Object2String(Type& object, string &str) {
shared_ptr<TMemoryBuffer> membuffer(
new
TMemoryBuffer());
shared_ptr<TProtocol> protocol(
new
TBinaryProtocol(membuffer));
object.write(protocol.get());
str.clear();
str = membuffer.getBufferAsString();
}
(2)使用thrift進行對象反序列化
//對str中保存的對象進行反序列化,保存到object中
template
<
typename
Type>
void
String2Object(string& buffer, Type &object) {
shared_ptr<TMemoryBuffer> membuffer(
new
TMemoryBuffer(
reinterpret_cast
<uint*>(buffer.data())));
shared_ptr<TProtocol> protocol(
new
TBinaryProtocol(membuffer));
object.read(protocol.get());
}
6. 編寫client和server
6.1 client端代碼編寫
Client編寫的方法分爲如下幾個步驟:
(1) 定義TTransport,爲你的client設置傳輸方式(如socket, http等)。
(2) 定義Protocal,使用裝飾模式(Decorator設計模式)封裝TTransport,爲你的數據設置編碼格式(如二進制格式,JSON格式等)
(3) 實例化client對象,調用服務接口。
說明:若是用戶在thrift文件中定義了一個叫${server_name}的service,則會生成一個叫${server_name}Client的對象,好比,我給出的例子中,thrift會自動生成一個叫BookServletClient的類,Client端的代碼編寫以下:
#include " gen-cpp/BookServlet.h" //必定要包含該頭文件
//其頭文件,其餘using namespace …….
int
main(
int
argc,
char
** argv) {
shared_ptr<TTransport> socket(
new
TSocket(
"localhost"
, 9090));
shared_ptr<TTransport> transport(
new
TBufferedTransport(socket));
shared_ptr<TProtocol> protocol(
new
TBinaryProtocol(transport));
example::BookServletClient client(protocol);
try
{
transport->open();
vector<example::Book_Info> books;
…...
client.Sender(books);
//RPC函數,調用serve端的該函數
transport->close();
}
catch
(TException &tx) {
printf
(
"ERROR: %s\n"
, tx.what());
}
}
6.2 Server端代碼編寫
(1) 定義一個TProcess,這個是thrift根據用戶定義的thrift文件自動生成的類
(2) 使用TServerTransport得到一個TTransport
(3) 使用TTransportFactory,可選地將原始傳輸轉換爲一個適合的應用傳輸(典型的是使用TBufferedTransportFactory)
(4) 使用TProtocolFactory,爲TTransport建立一個輸入和輸出
(5) 建立TServer對象(單線程,可使用TSimpleServer;對於多線程,用戶可以使用TThreadPoolServer或者TNonblockingServer),調用它的server()函數。
說明:thrift會爲每個帶service的thrift文件生成一個簡單的server代碼(樁),在例子中,thrift會生成BookServlet_server.skeleton.cpp,用戶能夠在這個文件基礎上實現本身的功能。
#include "gen-cpp/BookServlet.h"
#include <protocol/TBinaryProtocol.h>
#include <server/TSimpleServer.h>
#include <transport/TServerSocket.h>
#include <transport/TBufferTransports.h>
using
namespace
::apache::thrift;
using
namespace
::apache::thrift::protocol;
using
namespace
::apache::thrift::transport;
using
namespace
::apache::thrift::server;
using
boost::shared_ptr;
using
namespace
example;
class
BookServletHandler :
virtual
public
BookServletIf {
public
:
BookServletHandler() {
// Your initialization goes here
}
//用戶需實現這個接口
bool
Sender(
const
std::vector<example::Book_Info> & books) {
// Your implementation goes here
printf
(
"Sender\n"
);
}
//用戶需實現這個接口
void
Sender2(
const
std::vector<example::Book_Info> & books) {
// Your implementation goes here
printf
(
"Sender2\n"
);
}
};
int
main(
int
argc,
char
**argv) {
int
port = 9090;
shared_ptr<BookServletHandler> handler(
new
BookServletHandler());
shared_ptr<TProcessor> processor(
new
BookServletProcessor(handler));
shared_ptr<TServerTransport> serverTransport(
new
TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(
new
TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(
new
TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return
0;
}
7. 總結
至此,關於thrift框架的三篇文章已經所有完成,包括:
(1) Thrift框架介紹: Thrift框架介紹
(2) Thrift文件編寫方法: Thrift使用指南
(3) Thrift RPC使用方法:利用Thrift RPC編寫程序
與thrift相似的開源RPC框架還有google的protocal buffer,它雖然支持的語言比較少,但效率更高,於是受到愈來愈多的關注。
因爲thrift開源時間很早,經受了時間的驗證,於是許多系統更願意採用thrift,如Hadoop,Cassandra等。
附:thrift與protocal buffer比較
從上面的比較能夠看出,thrift勝在「豐富的特性「上,而protocal buffer勝在「文檔化」很是好上。在具體實現上,它們很是相似,都是使用惟一整數標記字段域,這就使得增長和刪除字段與不會破壞已有的代碼。
它們的最大區別是thrift支持完整的client/server RPC框架,而protocal buffer只會產生接口,具體實現,還須要用戶作大量工做。
另外,從序列化性能上比較,Protocal Buffer要遠遠優於thrift,具體可參考:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/?ca=drs-tp4608 。
8. 參考資料
(1) http://stuartsierra.com/2008/07/10/thrift-vs-protocol-buffers
(2) Thrift: Scalable Cross-Language Services Implementation. Mark Slee, Aditya Agarwal and Marc Kwiatkowski. Facebook
(3) Thrift網站:http://thrift.apache.org/
(4) Protocal Buffer網站:
http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html
原創文章,轉載請註明: 轉載自董的博客
本文連接地址: http://dongxicheng.org/search-engine/thrift-rpc/
4、Thrift 內部實現原理
Thrift由兩部分組成:編譯器(在compiler目錄下,採用C++編寫)和服務器(在lib目錄下),其中編譯器的做用是將用戶定義的thrift文件編譯生成對應語言的代碼,而服務器是事先已經實現好的、可供用戶直接使用的RPC Server(固然,用戶也很容易編寫本身的server)。同大部分編譯器同樣,Thrift編譯器(採用C++語言編寫)也分爲詞法分析、語法分析等步驟,Thrift使用了開源的flex和Bison進行詞法語法分析(具體見thrift.ll和thrift.yy),通過語法分析後,Thrift 根據對應語言的模板(在compiler\cpp\src\generate目錄下)生成相應的代碼。對於服務器實現而言,Thrift僅包含比較經典的服務器模型,好比單線程模型(TSimpleServer),線程池模型(TThreadPoolServer)、一個請求一個線程(TThreadedServer)和非阻塞模型(TNonblockingServer)等。本文將以C++爲例進行一個實例分析。
假設用戶編寫了如下Thrift文件:
struct LogInfo {
1: required string name,
2: optional string content,
}
service LogSender {
void SendLog(1:list<LogInfo> loglist);
}
用戶使用命令「thrift –gen cpp example.thrift」可生成C++代碼,該代碼包含如下文件:
example_constants.h
example_constants.cpp
example_types.h //struct定義
example_types.cpp //struct實現
LogSender.h //service定義
LogSender.cpp //service實現和LogSenderClient實現
LogSender_server.skeleton.cpp //一個實例RPC Server
用戶能夠這樣編寫Client:
shared_ptr socket(new TSocket(「8.8.8.8″, 9090));
shared_ptr transport(new TBufferedTransport(socket));
shared_ptr protocol(new TBinaryProtocol(transport));
LogSenderClient client(protocol);
try {
transport->open();
vector<LogInfo> logInfos;
LogInfo logInfo(「image」, 「10:9:0 visit:xxxxxx」);
logInfos.push_back(logInfo);
…..
client.SendLog(logInfos);
transport->close();
} catch (TException &tx) {
printf(「ERROR: %s\n」, tx.what());
}
爲了深刻分析這段代碼,咱們看一下client.SendLog()函數的內部實現(在LogSender.cpp中):
void LogSenderClient::SendLog(const std::vector<LogInfo> & loglist)
{
send_SendLog(loglist);
recv_SendLog();
}
void LogSenderClient::send_SendLog(const std::vector<LogInfo> & loglist)
{
int32_t cseqid = 0;
oprot_->writeMessageBegin(「SendLog」, ::apache::thrift::protocol::T_CALL, cseqid);
LogSender_SendLog_pargs args;
args.loglist = &loglist;
args.write(oprot_);
oprot_->writeMessageEnd();
oprot_->getTransport()->flush();
oprot_->getTransport()->writeEnd();
}
void LogSenderClient::recv_SendLog()
{
int32_t rseqid = 0;
std::string fname;
::apache::thrift::protocol::TMessageType mtype;
iprot_->readMessageBegin(fname, mtype, rseqid);
if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {
…..
}
if (mtype != ::apache::thrift::protocol::T_REPLY) {
……
}
if (fname.compare(「SendLog」) != 0) {
……
}
LogSender_SendLog_presult result;
result.read(iprot_);
iprot_->readMessageEnd();
iprot_->getTransport()->readEnd();
return;
}
閱讀上面的代碼,能夠看出,RPC函數SendLog()實際上被轉化成了兩個函數:send_SendLog和recv_SendLog,分別用於發送數據和接收結果。數據是以消息的形式表示的,消息頭部是RPC函數名,消息內容是RPC函數的參數。
咱們再進一步分析RPC Server端,一個server的編寫方法(在LogSender.cpp中)以下:
shared_ptr protocolFactory(new TBinaryProtocolFactory());
shared_ptr handler(new LogSenderHandler());
shared_ptr processor(new LogSenderProcessor(handler));
shared_ptr serverTransport(new TServerSocket(9090));
shared_ptr transportFactory(new TBufferedTransportFactory());
TSimpleServer server(processor,
serverTransport,
transportFactory,
protocolFactory);
printf(「Starting the server…\n」);
server.serve();
Server端最重要的類是LogSenderProcessor,它內部有一個映射關係processMap_,保存了全部RPC函數名到函數實現句柄的映射,對於LogSender而言,它只保存了一個RPC映射關係:
processMap_[" SendLog"] = &LogSenderProcessor::process_SendLog;
其中,process_SendLog是一個函數指針,它的實現以下:
void LogSenderProcessor::process_SendLog(int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot)
{
LogSender_SendLog_args args;
args.read(iprot);
iprot->readMessageEnd();
iprot->getTransport()->readEnd();
LogSender_SendLog_result result;
try {
iface_->SendLog(args.loglist);//調用用戶編寫的函數
} catch (const std::exception& e) {
……
}
oprot->writeMessageBegin(「SendLog」, ::apache::thrift::protocol::T_REPLY, seqid);
result.write(oprot);
oprot->writeMessageEnd();
oprot->getTransport()->flush();
oprot->getTransport()->writeEnd();
}
LogSenderProcessor中一個最重要的函數是process(),它是服務器的主體函數,服務器端(socket server)監聽到客戶端有請求到達後,會檢查消息類型,並檢查processMap_映射,找到對應的消息處理函數,並調用之(注意,這個地方能夠採用各類併發模型,好比one-request-one-thread,thread pool等)。
經過上面的分析能夠看出,Thrift最重要的組件是編譯器(採用C++編寫),它爲用戶生成了網絡通訊相關的代碼,從而大大減小了用戶的編碼工做。