一、内容概要
Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。
上一篇文章着重讲解关于thrift框架的介绍。本文主要讲述的对象是thrift文件,及其client和server的编写方法。
二、Thrift语法
1. Thrift类型
Thrift类型系统包括预定义基本类型,用户自定义结构体,容器类型,异常和服务定义
(1) 基本类型
bool:布尔类型(true or value),占一个字节
byte:有符号字节
i16:16位有符号整型
i32:32位有符号整型
i64:64位有符号整型
double:64位浮点数
string:未知编码或者二进制的字符串
注意,thrift不支持无符号整型,因为很多目标语言不存在无符号整型(如java)。
(2) 容器类型
Thrift容器与类型密切相关,它与当前流行编程语言提供的容器类型相对应,采用java泛型风格表示的。Thrift提供了3种容器类型:
list
set
map
容器中的元素类型可以是除了service意外的任何合法thrift类型(包括结构体和异常)。
(3) 结构体和异常
Thrift结构体在概念上同C语言结构体类型—-一种将相关属性聚集(封装)在一起的方式。在面向对象语言中,thrift结构体被转换成类。结构体使用struct关键字声明。
异常在语法和功能上类似于结构体,只不过异常使用关键字exception而不是struct关键字声明。但它在语义上不同于结构体—当定义一个RPC服务时,开发者可能需要声明一个远程方法抛出一个异常。
结构体和异常的声明将在下一节介绍。
(4) 服务
服务的定义方法在语法上等同于面向对象语言中定义接口。Thrift编译器会产生实现这些接口的client和server桩。具体参见后面的部分。
2. 枚举类型
enum NewsType {TOPNEWS, //aSPORTS = 2, //bTECNOLOGY = 3,NBA = 0xa, //cEDU = 5
} //dstruct News { 1: i32 id; 2: string title; 3: string content; 4: string source; 5: string author; 6: double score;7: i64 readNum;8: NewsType newsType = NewsType.SPORTS; //e
}
说明:
a. 编译器默认从0开始赋值
b. 可以赋予某个常量某个整数
c. 允许常量是十六进制整数
d. 末尾没有逗号
e. 给常量赋缺省值时,使用常量的全称
注意,不同于protocol buffer,thrift不支持枚举类嵌套,枚举常量必须是32位的正整数
2.3. 注释
Thrfit支持shell注释风格,C/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.
2.4. 命名空间
Thrift中的命名空间同C++中的namespace和java中的package类似,它们均提供了一种组织(隔离)代码的方式。因为每种语言均有自己的命名空间定义方式(如python中有module),thrift允许开发者针对特定语言定义namespace:
namespace cpp com.example.project // a
namespace java com.example.project // b
namespace py com.example.project //python
namespace php com.example.project //php
说明:
a.转化成namespace com { namespace example { namespace project {
b.转换成package com.example.project
2.5. 文件包含
Thrift允许thrift文件包含,用户需要使用thrift文件名作为前缀访问被包含的对象,如:
namespace java com.example.thrift.serviceinclude "NewsModel.thrift" //aservice DataSource { string helloWorld(1: string name), bool indexNews(1: NewsModel.News indexNews), //bNewsModel.News getNewsById(1: i32 newsId), map
}
说明:
a.thrift文件名要用双引号包含,末尾没有逗号或者分号
b.注意NewsModel前缀,IDL文件名作为前缀,而不是命名空间
2.6. 定义结构体
结构体由一系列域组成,每个域有唯一整数标识符,类型,名字和可选的缺省参数组成。如:
struct News { 1: required i32 id; //a2: required string title; //b3: string content; 4: string source; 5: string author; 6: double score = 4.2; //d7: i64 readNum;8: NewsType newsType = NewsType.SPORTS;10: optional User user; //c
} struct User { //e1: required i32 id; 2: required string userName;3: required string password;4: optional string nickName;
}
说明:
a.每个域有一个唯一的,正整数标识符
b.每个域可以标识为required或者optional(也可以不注明)
c.结构体可以包含其他结构体
d.域可以有缺省值
e.一个thrift中可定义多个结构体,并存在引用关系
规范的struct定义中的每个域均会使用required或者optional关键字进行标识。如果required标识的域没有赋值,thrift将给予提示。如果optional标识的域没有赋值,该域将不会被序列化传输。如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值。
与service不同,结构体不支持继承,即,一个结构体不能继承另一个结构体。
2.7. 定义服务
在流行的序列化/反序列化框架(如protocol buffer)中,thrift是少有的提供多语言间RPC服务的框架。
Thrift编译器会根据选择的目标语言为server产生服务接口代码,为client产生桩代码。
namespace java com.example.thrift.serviceinclude "NewsModel.thrift"
//“DataSource”与“{”之间需要有空格!!!
service DataSource { string helloWorld(1: string name), //abool indexNews(1: NewsModel.News indexNews), //bNewsModel.News getNewsById(1: i32 newsId), //cmap
}
说明:
a. 函数定义可以使用逗号或者分号标识结束
b. 参数可以是基本类型或者结构体,参数是只读的(const),不可以作为返回值!!!
c. 返回值可以是基本类型或者结构体
d. 返回值可以是void
注意,函数中参数列表的定义方式与struct完全一样
Service支持继承,一个service可使用extends关键字继承另一个service
三、 编译&产生代码
WINDOWS配置thrift开发环境
1.安装thrift:到thrift官网下载exe文件,下载地址:http://thrift.apache.org/download。然后将文件重命名为thrift.exe,拷贝到F:\thrift目录下(或者任何目录下),然后就可以在dos环境下使用了,如下:
F:\thrift>thrift -gen java D:\mywork\javaProject\thriftTest\test.thrift ,输出的java文件默认输出到当前目录下F:\thrift,也可以使用-o参数指定输出路径。
在Windows环境下进行编译时出现以下问题,如图:
原来是编码问题,最后统一thrift文件的编码为UTF-8就可以正常编译通过了。
四、编写Client与Service端
1.下载相关依赖包
a. libthrift.jar
b. slf4j-api.jar
c. slf4j-simple.jar
2.编写接口实现代码
package com.example.thrift.impl;import java.util.List;
import java.util.Map;import org.apache.thrift.TException;import com.example.thrift.model.News;
import com.example.thrift.service.DataSource;public class DataSourceHandler implements DataSource.Iface{@Overridepublic String helloWorld(String name) throws TException {// TODO Auto-generated method stubreturn "hello "+name+",welcom to thrift!";}@Overridepublic boolean indexNews(News indexNews) throws TException {// TODO Auto-generated method stubreturn false;}@Overridepublic News getNewsById(int newsId) throws TException {News news = new News();news.setId(1000);news.setAuthor("Ricky Feng");news.setContent("thrift demo");return news;}@Overridepublic Map
3.编写server代码
package com.example.thrift;import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
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.example.thrift.impl.DataSourceHandler;
import com.example.thrift.service.DataSource;public class HelloServer {public static final int SERVER_PORT = 8090;public static void main(String[] args) {startServer();}private static void startServer() {try {System.out.println("HelloServer TSimpleServer start ....");TProcessor tprocessor = new DataSource.Processor
}
4.编写client代码
package com.example.thrift;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 org.apache.thrift.transport.TTransportException;import com.example.thrift.service.DataSource;public class HelloClient {public static final String SERVER_IP = "localhost";public static final int SERVER_PORT = 8090;public static final int TIMEOUT = 30000;public static void main(String[] args) {startClient();}private static void startClient() {TTransport transport = null;try {System.out.println("HelloClient start ....");transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);// 协议要和服务端一致TProtocol protocol = new TBinaryProtocol(transport);// TProtocol protocol = new TCompactProtocol(transport);// TProtocol protocol = new TJSONProtocol(transport);DataSource.Client client = new DataSource.Client(protocol);transport.open();String result = client.helloWorld("Ricky");System.out.println("Thrify client result =: " + result);} catch (TTransportException e) {e.printStackTrace();} catch (TException e) {e.printStackTrace();} finally {if (null != transport) {transport.close();}}}
}
先运行HelloServer启动服务,然后运行HelloClient就可以看见控制台打印:
HelloClient start ....
Thrify client result =: hello Ricky,welcom to thrift!
关于Thrift使用的详细示例可以参考Apache Thrift网站上的Tutorial,地址:http://thrift.apache.org/tutorial/java
最后附上示例代码下载地址:http://download.csdn.net/detail/fx_sky/7499361