转自:http://blog.163.com/scuqifuguang@126/blog/static/171370086201362273929684/
第一部分: thrift的安装使用
1.1 简介
thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
1.2 下载
http://thrift.apache.org/download/ (我下载的0.5.0版本)
1.3 安装
注:在安装前请保证g++, bison 等依赖包已经安装。
sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ python-dev libssl-dev
1)tar zxf thrift-XXX.tar.gz
2)./configure
or
./configure --with-php=no --with-php_extension=no --with-python=no
在进行下一步之前,请确认是否安装python2.6-dev,若无,请安装
3)make
第二部分:Thrift语法
2.1 类型
Thrift类型系统包括预定义基本类型,用户自定义结构体,容器类型,异常和服务定义。
2.1.1 基本类型
bool: 布尔值 (true or false), one byte
byte: 有符号字节
i16: 16位有符号整型
i32: 32位有符号整型
i64: 64位有符号整型
double: 64位浮点型
string: Encoding agnostic text or binary string
Note that: Thrift不支持无符号整型,因为Thrift目标语言没有无符号整型,无法转换。
2.1.2 容器(Containers)
Thrift容器与流行编程语言的容器类型相对应,采用Java泛型风格。它有3种可用容器类型:
list
set
map
容器中元素类型可以是除了service外的任何合法Thrift类型(包括结构体和异常)。
Thrift结构体在概念上类似于(similar to)C语言结构体类型--将相关属性封装在一起的简便方式。Thrift结构体将会被转换成面向对象语言的类。
异常在语法和功能上类似于(equivalent to)结构体,差别是异常使用关键字exception而不是struct声明。但它在语义上不同于结构体:当定义一个RPC服务时,开发者可能需要声明一个远程方法抛出一个异常。
结构体和异常的声明将在下一节介绍。
服务的定义方法在语义(semantically)上等同于面向对象语言中的接口。Thrift编译器会产生执行这些接口的client和server stub。具体参见下一节。
Thrift支持C/C++类型定义。
typedef i32 MyInteger // a typedef T ReT // b
说明:a. 末尾没有逗号。b. struct也可以使用typedef。
很多语言都有枚举,意义都一样。比如,当定义一个消息类型时,它只能是预定义的值列表中的一个,可以用枚举实现。
enum TweetType { TWEET, // (1) RETWEET = 2, // (2) DM = 0xa, // (3) REPLY // (4) } struct Tweet { 1: required i32 userId; 2: required string userName; 3: required string text; 4: optional Location loc; 5: optional TweetType tweetType = TweetType.TWEET; // (5) 16: optional string language = "english" }
说明:
(1). 编译器默认从0开始赋值
(2). 可以赋予某个常量某个整数
(3). 允许常量是十六进制整数
(4). 末尾没有分号
(5). 给常量赋缺省值时,使用常量的全称
注意,不同于protocal buffer,thrift不支持枚举类嵌套,枚举常量必须是32位的正整数
Thrift支持shell风格, C多行风格和Java/C++单行风格。
# This is a valid comment. /* * This is a multi-line comment. * Just like in C. */ // C++/Java style single-line comments work just as well.
Thrift中的命名空间类似于C++中的namespace和java中的package,它们提供了一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。
由于每种语言均有自己的命名空间定义方式(如python中有module), thrift允许开发者针对特定语言定义namespace:
namespace cpp com.example.project // (1) namespace java com.example.project // (2) namespace php com.example.project
(1). 转化成namespace com { namespace example { namespace project {
(2). 转换成package com.example.project
便于管理、重用和提高模块性/组织性,我们常常分割Thrift定义在不同的文件中。包含文件搜索方式与c++一样。Thrift允许文件包含其它thrift文件,用户需要使用thrift文件名作为前缀访问被包含的对象,如:
include "tweet.thrift" // (1) ... struct TweetSearchResult { 1: tweet.Tweet tweet; // (2) }
说明:
(1). thrift文件名要用双引号包含,末尾没有逗号或者分号
(2). 注意tweet前缀
Thrift允许定义跨语言使用的常量,复杂的类型和结构体可使用JSON形式表示。
const i32 INT_CONST = 1234; // (1)
说明:
(1) 分号可有可无。支持16进制。
struct是Thrift IDL中的基本组成块,由域组成,每个域有唯一整数标识符,类型,名字和可选的缺省参数组成。如定义一个类似于Twitter服务:
struct Tweet { 1: required i32 userId; // (1) 2: required string userName; // (2) 3: required string text; 4: optional Location loc; // (3) 16: optional string language = "english" // (4) } struct Location { // (5) 1: required double latitude; 2: required double longitude; }
(1) 每个域有一个唯一的正整数标识符;
(2) 每个域可标识为required或optional;
(3) 结构体可以包含其它结构体
(4) 域可有默认值,与required或optional无关。
(5) Thrift文件可以定义多个结构体,并在同一文件中引用,也可加入文件限定词在其它Thrift文件中引用。
如上所见,消息定义中的每个域都有一个唯一数字标签,这些数字标签在传输时用来确定域,一旦使用消息类型,标签不可改变。(随着项目的进展,可以要变更Thrift文件,最好不要改变原有的数字标签)
规范的struct定义中的每个域均会使用required或者 optional关键字进行标识。如果required标识的域没有赋值,Thrift将给予提示;如果optional标识的域没有赋值,该域将不会被 序列化传输;如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值;如果某个optional标识域有缺省值或者用户已经重新赋值,而不设置它的__isset为true,也不会被序列化传输。(不被序列化传输的后果是什么?为空为零?还是默认值,下次试试)
与services不同,结构体不支持继承。
在流行的序列化/反序列化框架(如protocal buffer)中,Thrift是少有的提供多语言间RPC服务的框架。这是Thrift的一大特色。
Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生stubs。
service Twitter { // A method definition looks like C code. It has a return type, arguments, // and optionally a list of exceptions that it may throw. Note that argument // lists and exception list are specified using the exact same syntax as // field lists in structs. void ping(), // (1) bool postTweet(1:Tweet tweet); // (2) TweetSearchResult searchTweets(1:string query); // (3) // The 'oneway' modifier indicates that the client only makes a request and // does not wait for any response at all. Oneway methods MUST be void. oneway void zip() // (4) }
Note that:参数列表的定义与结构体一样。服务支持继承。
第三部分:服务器和客户端的编写与运行(java实现)
3.1 编写thrift文件
TestThrift.thrift
namespace java com.xiaomi.winwill.thrift struct Blog { 1:string topic 2:binary content 3:i64 createTime 4:string id 5:string ipAddress 6:map<string,string> props } service ThriftCase { i32 testCase1(1:i32 num1, 2:i32 num2,3:string num3) list
testCase2(1:map<string,string> num1) void testCase3() void testCase4(1:list<Blog> blog) }
3.2 生成代码 (以java语言为例)
thrift -gen java TestThrift.thrift
3.3 创建工程
在eclipse下新建一个java project,将生成的代码整个文件夹(com/xiaomi/winwill/service/*)拷贝到工程的src目录下。
3.4 实现接口
TestThrift.thrift文件中定义类一个service(ThriftCase),生成java代码之后将会变成一个接口,这些接口的作用是实现跨平台通信,但是真正的业务逻辑并未实现,所以,要做什么?怎么做?这些详细的设计应该由我们自己来实现。在工程的com.xiaomi.winwill.service包中创建一个类:Business.java,该类实现ThriftCase的Iface接口:
/** * Business.java * [CopyRight] * @date 2013-7-22 下午5:27:55 */ package com.xiaomi.winwill.service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.thrift.TException; import com.xiaomi.winwill.thrift.Blog; import com.xiaomi.winwill.thrift.ThriftCase; /** * @author qifuguang */ public class Business implements ThriftCase.Iface { private Set<String> keySet; @Override public int testCase1(int num1, int num2, String num3) throws TException { System.out.println("#1: "); if(num3.equals("+")){ System.out.println(num1+num3+num2+"="+(num1+num2)); return num1 + num2; } else if(num3.equals("-")){ System.out.println(num1+num3+num2+"="+(num1-num2)); return num1 - num2; } else if(num3.equals("*")){ System.out.println(num1+num3+num2+"="+(num1*num2)); return num1 * num2; } else if(num3.equals("/")){ if(num2 == 0){ System.out.println("error, can not divided by zero!"); return 0; } else{ System.out.println(num1+num3+num2+"="+(num1/num2)); return num1 / num2; } } return 0; } @Override public List<String> testCase2(Map<String, String> num1) throws TException { System.out.println("#2: "); List<String> res = new ArrayList<String>(); keySet = num1.keySet(); for(String str:keySet){ res.add(str); } System.out.println(res); return res; } @Override public void testCase3() throws TException { System.out.println("#3: "); System.out.println("Output nothing!"); } @Override public void testCase4(List<Blog> blog) throws TException { System.out.println("#4: "); for (Blog blog2 : blog) { System.out.println("id: "+blog2.getId()); System.out.println("ipAddress: "+blog2.getIpAddress()); System.out.println("createTime: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(blog2.getCreateTime())); System.out.println("topic: "+blog2.getTopic()); } } }
至于上述代码中的四个testcase方法具体实现什么功能并不重要,只是做个演示。
3.5 编写服务器代码
/** * Server.java * [CopyRight] * @date 2013-7-22 下午5:26:24 */ package com.xiaomi.winwill.service; import java.io.IOException; import java.net.ServerSocket; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TSimpleServer; import org.apache.thrift.transport.TServerSocket; import org.apache.thrift.transport.TTransportException; import com.xiaomi.winwill.thrift.ThriftCase; public class Server { public static void main(String[] args) throws TTransportException, IOException { ServerSocket socket = new ServerSocket(7912); TServerSocket serverTransport = new TServerSocket(socket); ThriftCase.Processor processor = new ThriftCase.Processor(new Business()); TServer server = new TSimpleServer(processor, serverTransport); System.out.println("Running server..."); server.serve(); } }
该类实现服务器的监听工作,创建一个TServerSocket,监听本机的7912端口。
3.6 编写客户端代码
/** * Client.java * [CopyRight] * @date 2013-7-22 下午5:34:30 */ package com.xiaomi.winwill.service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import com.xiaomi.winwill.thrift.Blog; import com.xiaomi.winwill.thrift.ThriftCase;
public class Client { public static void main(String[] args) throws TException { Map<String, String> map = new HashMap<String, String>(); List<Blog> blogs = new ArrayList<Blog>(); TTransport transport = new TSocket("localhost", 7912); TProtocol protocol = new TBinaryProtocol(transport); ThriftCase.Client client = new ThriftCase.Client(protocol); Blog blog = new Blog(); blog.createTime = System.currentTimeMillis(); blog.id = "SCUQIFUGUANG"; blog.ipAddress = "127.0.0.1"; blog.topic = "this is blog topic"; blogs.add(blog); transport.open(); map.put("MyBlog", "http://blog.163.com/scuqifuguang@126/"); System.out.println("Client calling"); client.testCase1(10, 21, "+"); client.testCase2(map); client.testCase3(); client.testCase4(blogs); transport.close(); } }
该类定义一个trasport,连接本机的7912端口,与服务器通讯,实现远程调用(虽然该例是在同一台电脑,不同电脑也类似)。
3.7 添加依赖
工程中很多地方都import了很多陌生的类,这些类包含在一下几个jar包中:
3.7 工程目录结构
准备工作全部做好,看看整个工程的目录结构:
3.8 运行
首先运行Server:
从运行结果可以看出,Client端已经成功地与服务器通讯,并且远程调用了服务器的四个TestCase方法。