1. 內(nèi)容概要
本文檔比較全面的介紹了thrift語法,代碼生成結(jié)構(gòu)和應(yīng)用經(jīng)驗。本文主要講述的對象是thrift文件,并未涉及其client和server的編寫方法。
2. 語法參考
2.1 Types
Thrift類型系統(tǒng)包括預定義基本類型,用戶自定義結(jié)構(gòu)體,容器類型,異常和服務(wù)定義
(1) 基本類型
12345678910111213 |
|
注意,thrift不支持無符號整型,因為很多目標語言不存在無符號整型(如java)。
(2) 容器類型
Thrift容器與類型密切相關(guān),它與當前流行編程語言提供的容器類型相對應(yīng),采用java泛型風格表示的。Thrift提供了3種容器類型:
List<t1>:一系列t1類型的元素組成的有序表,元素可以重復
Set<t1>:一系列t1類型的元素組成的無序表,元素唯一
Map<t1,t2>:key/value對(key的類型是t1且key唯一,value類型是t2)。
容器中的元素類型可以是除了service以外的任何合法thrift類型(包括結(jié)構(gòu)體和異常)。
(3) 結(jié)構(gòu)體和異常
Thrift結(jié)構(gòu)體在概念上同C語言結(jié)構(gòu)體類型—— 一種將相關(guān)屬性聚集(封裝)在一起的方式。在面向?qū)ο笳Z言中,thrift結(jié)構(gòu)體被轉(zhuǎn)換成類。
異常在語法和功能上類似于結(jié)構(gòu)體,只不過異常使用關(guān)鍵字exception而不是struct關(guān)鍵字聲明。但它在語義上不同于結(jié)構(gòu)體——當定義一個RPC服務(wù)時,開發(fā)者可能需要聲明一個遠程方法拋出一個異常。
結(jié)構(gòu)體和異常的聲明將在下一節(jié)介紹。
(4) 服務(wù)
服務(wù)的定義方法在語法上等同于面向?qū)ο笳Z言中定義接口。Thrift編譯器會產(chǎn)生實現(xiàn)這些接口的client和server樁。具體參見下一節(jié)。
(5) 類型定義
Thrift支持C/C++風格的typedef:
123 |
|
說明:
a. 末尾沒有逗號
b. struct可以使用typedef
2.2 枚舉類型
可以像C/C++那樣定義枚舉類型,如:
123456789101112131415161718192021222324252627 |
|
說明:
a. 編譯器默認從0開始賦值
b. 可以賦予某個常量某個整數(shù)
c. 允許常量是十六進制整數(shù)
d. 末尾沒有逗號
e. 給常量賦缺省值時,使用常量的全稱
注意,不同于PRotocol buffer,thrift不支持枚舉類嵌套,枚舉常量必須是32位的正整數(shù)
2.3 注釋
Thrfit支持shell注釋風格,C/C++語言中單行或者多行注釋風格
1234567891011 |
|
2.4 命名空間
Thrift中的命名空間同C++中的namespace和java中的package類似,它們均提供了一種組織(隔離)代碼的方式。因為每種語言均有自己的命名空間定義方式(如python中有module),thrift允許開發(fā)者針對特定語言定義namespace:
123 |
|
說明:
a. 轉(zhuǎn)化成namespace com { namespace example { namespace project {
b. 轉(zhuǎn)換成package com.example.project
2.5 文件包含
Thrift允許thrift文件包含,用戶需要使用thrift文件名作為前綴訪問被包含的對象,如:
123456789 |
|
說明:
a. thrift文件名要用雙引號包含,末尾沒有逗號或者分號
b. 注意tweet前綴
2.6 常量
Thrift允許用戶定義常量,復雜的類型和結(jié)構(gòu)體可使用JSON形式表示。
123 |
|
說明:
a. 分號是可選的,可有可無;支持十六進制賦值。
2.7 定義結(jié)構(gòu)體
結(jié)構(gòu)體由一系列域組成,每個域有唯一整數(shù)標識符,類型,名字和可選的缺省參數(shù)組成。如:
123456789101112131415161718192021 |
|
說明:
a. 每個域有一個唯一的,正整數(shù)標識符
b. 每個域可以標識為required或者optional(也可以不注明)
c. 結(jié)構(gòu)體可以包含其他結(jié)構(gòu)體
d. 域可以有缺省值
e. 一個thrift中可定義多個結(jié)構(gòu)體,并存在引用關(guān)系
規(guī)范的struct定義中的每個域均會使用required或者optional關(guān)鍵字進行標識。如果required標識的域沒有賦值,thrift將給予提示。如果optional標識的域沒有賦值,該域?qū)⒉粫恍蛄谢瘋鬏?。如果某個optional標識域有缺省值而用戶沒有重新賦值,則該域的值一直為缺省值。
與service不同,結(jié)構(gòu)體不支持繼承,即,一個結(jié)構(gòu)體不能繼承另一個結(jié)構(gòu)體。
2.8 定義服務(wù)
在流行的序列化/反序列化框架(如protocol buffer)中,thrift是少有的提供多語言間RPC服務(wù)的框架。
Thrift編譯器會根據(jù)選擇的目標語言為server產(chǎn)生服務(wù)接口代碼,為client產(chǎn)生樁代碼。
1234567891011121314151617181920 |
|
說明:
a. 函數(shù)定義可以使用逗號或者分號標識結(jié)束
b. 參數(shù)可以是基本類型或者結(jié)構(gòu)體,參數(shù)是只讀的(const),不可以作為返回值!!!
c. 返回值可以是基本類型或者結(jié)構(gòu)體
d. 返回值可以是void
注意,函數(shù)中參數(shù)列表的定義方式與struct完全一樣
Service支持繼承,一個service可使用extends關(guān)鍵字繼承另一個service
3. 產(chǎn)生代碼
本節(jié)介紹thrift產(chǎn)生各種目標語言代碼的方式。本節(jié)從幾個基本概念開始,逐步引導開發(fā)者了解產(chǎn)生的代碼是怎么樣組織的,進而幫助開發(fā)者更快地明白thrift的使用方法。
概念
Thrift的網(wǎng)絡(luò)棧如下所示:

3.1 Transport
Transport層提供了一個簡單的網(wǎng)絡(luò)讀寫抽象層。這使得thrift底層的transport從系統(tǒng)其它部分(如:序列化/反序列化)解耦。以下是一些Transport接口提供的方法:
123456789 |
|
除了以上幾個接口,Thrift使用ServerTransport接口接受或者創(chuàng)建原始transport對象。正如名字暗示的那樣,ServerTransport用在server端,為到來的連接創(chuàng)建Transport對象。
1234567 |
|
3.2 Protocol
Protocol抽象層定義了一種將內(nèi)存中數(shù)據(jù)結(jié)構(gòu)映射成可傳輸格式的機制。換句話說,Protocol定義了datatype怎樣使用底層的Transport對自己進行編解碼。因此,Protocol的實現(xiàn)要給出編碼機制并負責對數(shù)據(jù)進行序列化。
Protocol接口的定義如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
|
下面是一些對大部分thrift支持的語言均可用的protocol:
(1) binary:簡單的二進制編碼
(2) Compact
(3) Json
3.3 Processor
Processor封裝了從輸入數(shù)據(jù)流中讀數(shù)據(jù)和向數(shù)據(jù)數(shù)據(jù)流中寫數(shù)據(jù)的操作。讀寫數(shù)據(jù)流用Protocol對象表示。Processor的結(jié)構(gòu)體非常簡單:
12345 |
|
與服務(wù)相關(guān)的processor實現(xiàn)由編譯器產(chǎn)生。Processor主要工作流程如下:從連接中讀取數(shù)據(jù)(使用輸入protocol),將處理授權(quán)給handler(由用戶實現(xiàn)),最后將結(jié)果寫到連接上(使用輸出protocol)。
3.4 Server
Server將以上所有特性集成在一起:
(1) 創(chuàng)建一個transport對象
(2) 為transport對象創(chuàng)建輸入輸出protocol
(3) 基于輸入輸出protocol創(chuàng)建processor
(4) 等待連接請求并將之交給processor處理
3.5 應(yīng)用舉例
下面,我們討論thrift文件產(chǎn)生的特定語言代碼。下面給出thrift文件描述:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
|
(1) Java語言
(a) 產(chǎn)生的文件
一個單獨的文件(Constants.java)包含所有的常量定義。
每個結(jié)構(gòu)體,枚舉或者服務(wù)各占一個文件
$ tree gen-java
`– thrift
`– example
|– Constants.java
|– Location.java
|– Tweet.java
|– TweetSearchResult.java
|– TweetType.java
`– Twitter.java
(b) 類型
thrift將各種基本類型和容器類型映射成java類型:
| 12345678910111213141516171819 | 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,它只使用原始類型,如,在上面的例子中,產(chǎn)生的代碼中,TweetSearchResult會被還原成list<Tweet> tweets
(d) Enum
Thrift直接將枚舉類型映射成java的枚舉類型。用戶可以使用geValue方法獲取枚舉常量的值。此外,編譯器會產(chǎn)生一個findByValue方法獲取枚舉對應(yīng)的數(shù)值。
(e) 常量
Thrift把所有的常量放在一個叫Constants的public類中,每個常量修飾符是public static final。
(2) C++語言
(a) 產(chǎn)生的文件
所有變量均存放在一個.cpp/.h文件對中
所有的類型定義(枚舉或者結(jié)構(gòu)體)存放到另一個.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. 實踐經(jīng)驗
thrift文件內(nèi)容可能會隨著時間變化的。如果已經(jīng)存在的消息類型不再符合設(shè)計要求,比如,新的設(shè)計要在message格式中添加一個額外字段,但你仍想使用以前的thrift文件產(chǎn)生的處理代碼。如果想要達到這個目的,只需:
(1) 不要修改已存在域的整數(shù)編號
(2) 新添加的域必須是optional的,以便格式兼容。對于一些語言,如果要為optional的字段賦值,需要特殊處理,比如對于C++語言,要為
123456789 |
|
中的optional字段age賦值,需要將它的__isset值設(shè)為true,這樣才能序列化并傳輸或者存儲(不然optional字段被認為不存在,不會被傳輸或者存儲),
如:
123456789 |
|
(3) 非required域可以刪除,前提是它的整數(shù)編號不會被其他域使用。對于刪除的字段,名字前面可添加“OBSOLETE_”以防止其他字段使用它的整數(shù)編號。
(4) thrift文件應(yīng)該是unix格式的(windows下的換行符與unix不同,可能會導致你的程序編譯不過),如果是在window下編寫的,可使用dos2unix轉(zhuǎn)化為unix格式。
(5) 貌似當前的thrift版本(0.6.1)不支持常量表達式的定義(如 const i32 DAY = 24 * 60 * 60),這可能是考慮到不同語言,運算符不盡相同。
新聞熱點
疑難解答