Thrift 原理與使用實例

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

image

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代碼

image

在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的網絡棧以下所示:

image

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比較

image

從上面的比較能夠看出,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++編寫),它爲用戶生成了網絡通訊相關的代碼,從而大大減小了用戶的編碼工做。

相關文章
相關標籤/搜索