Thrift 入门指南

一、内容概要
          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) 基本类型

<span style="font-size:14px;">bool:布尔类型(true or value),占一个字节 
byte:有符号字节 
i16:16位有符号整型 
i32:32位有符号整型 
i64:64位有符号整型 
double:64位浮点数 
string:未知编码或者二进制的字符串</span>

注意,thrift不支持无符号整型,因为很多目标语言不存在无符号整型(如java)。
(2) 容器类型
Thrift容器与类型密切相关,它与当前流行编程语言提供的容器类型相对应,采用java泛型风格表示的。Thrift提供了3种容器类型:
list<t1>:一系列t1类型的元素组成的有序表,元素可以重复。相当于Java中的List
set<t1>:一系列t1类型的元素组成的无序表,元素唯一。相当于Java中的Set
map<t1,t2>:key/value对(key的类型是t1且key唯一,value类型是t2)。相当于Java中的Map
容器中的元素类型可以是除了service意外的任何合法thrift类型(包括结构体和异常)。
(3)  结构体和异常
Thrift结构体在概念上同C语言结构体类型—-一种将相关属性聚集(封装)在一起的方式。在面向对象语言中,thrift结构体被转换成类。结构体使用struct关键字声明。
异常在语法和功能上类似于结构体,只不过异常使用关键字exception而不是struct关键字声明。但它在语义上不同于结构体—当定义一个RPC服务时,开发者可能需要声明一个远程方法抛出一个异常。
结构体和异常的声明将在下一节介绍。
(4)  服务
服务的定义方法在语法上等同于面向对象语言中定义接口。Thrift编译器会产生实现这些接口的client和server桩。具体参见后面的部分。

2. 枚举类型

<span style="font-size:14px;">enum NewsType {
	TOPNEWS,	//a
	SPORTS = 2,	//b
	TECNOLOGY = 3,
	NBA = 0xa,	//c
	EDU = 5		
}	//d

struct 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
} </span>

说明:
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:
<span style="font-size:14px;">namespace cpp com.example.project  // a
namespace java com.example.project // b
namespace py com.example.project	//python
namespace php com.example.project  	//php</span>

说明:
a.转化成namespace com { namespace example { namespace project {
b.转换成package com.example.project
2.5. 文件包含
Thrift允许thrift文件包含,用户需要使用thrift文件名作为前缀访问被包含的对象,如:

namespace java com.example.thrift.service

include "NewsModel.thrift"	//a

service DataSource {  

	string helloWorld(1: string name), 

	bool indexNews(1: NewsModel.News indexNews), //b
	
	NewsModel.News getNewsById(1: i32 newsId), 
	
	map<string,NewsModel.News> getNewsMap(1: i32 newsType), 
	
	list<NewsModel.News> getNewsList(1: i32 newsType), 
}  

说明:
a.thrift文件名要用双引号包含,末尾没有逗号或者分号
b.注意NewsModel前缀,IDL文件名作为前缀,而不是命名空间

2.6. 定义结构体
结构体由一系列域组成,每个域有唯一整数标识符,类型,名字和可选的缺省参数组成。如:

struct News {  
	1: required i32 id;  //a
	2: required string title;  //b
	3: string content;  
	4: string source;  
	5: string author;  
	6: double score = 4.2;	//d
	7: i64 readNum;
	8: NewsType newsType = NewsType.SPORTS;
	10: optional User user;	//c
} 


struct User {	//e
	1: 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.service

include "NewsModel.thrift"
//“DataSource”与“{”之间需要有空格!!!
service DataSource {  

	string helloWorld(1: string name), 	//a

	bool indexNews(1: NewsModel.News indexNews), 	//b
	
	NewsModel.News getNewsById(1: i32 newsId), //c
	
	map<string,NewsModel.News> getNewsMap(1: i32 newsType), 
	
	list<NewsModel.News> getNewsList(1: i32 newsType), 
	
	// ”oneway”标识符表示client发出请求后不必等待回复(非阻塞)直接进行下面的操作, 
	// ”oneway”方法的返回值必须是void 
	oneway void zip()   // d
}  

说明:
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 入门指南_第1张图片
原来是编码问题,最后统一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{

	@Override
	public String helloWorld(String name) throws TException {
		// TODO Auto-generated method stub
		return "hello "+name+",welcom to thrift!";
	}

	@Override
	public boolean indexNews(News indexNews) throws TException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public News getNewsById(int newsId) throws TException {
		
		News news = new News();
		news.setId(1000);
		news.setAuthor("Ricky Feng");
		news.setContent("thrift demo");
		
		return news;
	}

	@Override
	public Map<String, News> getNewsMap(int newsType) throws TException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<News> getNewsList(int newsType) throws TException {
		// TODO Auto-generated method stub
		return null;
	}

}

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<DataSource.Iface>(new DataSourceHandler());
			// 简单的单线程服务模型,一般用于测试
			TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
			TServer.Args tArgs = new TServer.Args(serverTransport);
			tArgs.processor(tprocessor);
			tArgs.protocolFactory(new TBinaryProtocol.Factory());
			TServer server = new TSimpleServer(tArgs);
			server.serve();
		} catch (TTransportException e) {
			e.printStackTrace();
		}

	}
}

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




你可能感兴趣的:(thrift)