原文链接:thrift入门 转载请注明出处~
Thrift简介
什么是thrift
简单来说,是Facebook公布的一款开源跨语言的RPC框架.
什么是RPC框架?
RPC (Remote Procedure Call Protocal),远程过程调用协议
RPC, 远程过程调用直观说法就是A通过网络调用B的过程方法。
简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。
客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
早期单机时代,一台电脑上运行多个进程,大家各干各的,老死不相往来。假如A进程需要一个画图的功能,B进程也需要一个画图的功能,程序员就必须为两个进程都写一个画图的功能。这不是整人么?于是就出现了IPC(Inter-process communication,单机中运行的进程之间的相互通信)。OK,现在A既然有了画图的功能,B就调用A进程上的画图功能好了,程序员终于可以偷下懒了。
到了网络时代,大家的电脑都连起来了。以前程序只能调用自己电脑上的进程,能不能调用其他机器上的进程呢?于是就程序员就把IPC扩展到网络上,这就是RPC(远程过程调用)了。现在不仅单机上的进程可以相互通信,多机器中的进程也可以相互通信了。要知道实现RPC很麻烦呀,什么多线程、什么Socket、什么I/O,都是让咱们普通程序员很头疼的事情。于是就有牛人开发出RPC框架(比如,CORBA、RMI、Web Services、RESTful Web Services等等)。OK,现在可以定义RPC框架的概念了。简单点讲,RPC框架就是可以让程序员来调用远程进程上的代码一套工具。有了RPC框架,咱程序员就轻松很多了,终于可以逃离多线程、Socket、I/O的苦海了。
thrift的跨语言特型
thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口,这些内容写在以.thrift结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者,比如java开发者,就可以生成java代码,c++开发者可以生成c++代码,生成的代码中不但包含目标语言的接口定义,方法,数据类型,还包含有RPC协议层和传输层的实现代码.
thrift的协议栈结构
thrift是一种c/s的架构体系。在最上层是用户自行实现的业务逻辑代码。
第二层是由thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。TServer主要任务是高效的接受客户端请求,并将请求转发给Processor处理。Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。
从TProtocol以下部分是thirft的传输协议和底层I/O通信。TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。
Thrift安装
安装环境:window 7
- 在官网上下载thrift-0.9.3.exe包到一个新建文件夹(博主的文件夹名称为Thrift)中
- 然后将此文件夹放到环境变量Path中。例如博主就是将D:Thrift添加到Path中
- cmd,打开终端,输入
thrift -version
,即可看到相应的版本号,就算是成功安装啦
ThriftDemo
下面,来做个小Demo来熟悉Thrift的使用流程
- 首先在一个目录下,创建一个文件,博主是用NotePad++创建的,用windows自带的记事本貌似也是可以的,这里创建了一个thrift脚本,命名为login.thrift,内容如下
namespace java com.game.lll.thrift
struct Request {
1: string username;
2: string password;
}
exception RequestException {
1: required i32 code;
2: optional string reason;
}
// 服务名
service LoginService {
string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
}
- 终端进入当前文件夹,在终端输入命令
thrift -gen java login.thrift
。当前目录下会生成一个gen-java文件夹,文件夹下会按照namespace定义的路径名一层层生成文件夹,到最里层的文件夹里可以看到生成的3个java类Request.java
,RequestException.java
,LoginService.java
- 用IDEA/Eclipse新建一个工程,并为此工程添加依赖。博主创建的是Maven工程,就在pom.xml里面添加依赖,如下
<!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-nop -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.21</version>
</dependency>
- 将目录中的代码拷贝到工程的classpath下,IDEA中的classpath就是source所处的文件夹(会有颜色标识,博主的版本是蓝色文件夹)
- 创建LoginServiceImpl.java类,实现在LoginService.Iface接口
/**
* Created by CiCi on 2017/5/16.
*/
import org.apache.thrift.TException;
import com.game.lll.thrift.LoginService;
import com.game.lll.thrift.Request;
import com.game.lll.thrift.RequestException;
public class LoginServiceImpl implements LoginService.Iface{
@Override
public String doAction(Request request) throws RequestException,TException {
// TODO Auto-generated method stub
System.out.println("hahaha");
System.out.println("username:"+request.getUsername());
System.out.println("password:"+request.getPassword());
return request.getUsername()+request.getPassword();
}
}
- 新建服务器端LoginMain.java
/**
* Created by CiCi on 2017/5/16.
*/
import java.net.ServerSocket;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import com.game.lll.thrift.LoginService;
import com.game.lll.thrift.LoginService.Processor;
public class LoginMain {
public static void main(String[] args) throws Exception {
// Transport
ServerSocket socket = new ServerSocket(8888);
TServerSocket serverTransport = new TServerSocket(socket);
// Processor
LoginService.Processor processor = new Processor(new LoginServiceImpl());
TServer.Args tServerArgs = new TServer.Args(serverTransport);
tServerArgs.processor(processor);
// Server
TServer server = new TSimpleServer(tServerArgs);
System.out.println("Starting the simple server...");
server.serve();
}
}
- 新建客户端
/**
* Created by CiCi on 2017/5/16.
*/
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.game.lll.thrift.LoginService;
import com.game.lll.thrift.Request;
public class ClientMain {
public static void main(String[] args) throws Exception {
TTransport transport = null;
try {
// 创建TTransport
transport = new TSocket("localhost", 8888);
// 创建TProtocol 协议要与服务端一致
TProtocol protocol = new TBinaryProtocol(transport);
// 创建client
LoginService.Client client = new LoginService.Client(protocol);
transport.open(); // 建立连接
Request request = new Request().setUsername("liulongling").setPassword("123456");
// client调用server端方法
System.out.println(client.doAction(request));
}catch (Exception e) {
e.printStackTrace();
}finally {
transport.close(); // 请求结束,断开连接
}
}
}
- 运行服务器端,控制台输出结果“Starting the simple server...”
- 运行客户端,控制台输出结果
username:lalala
password:123456
Thrift使用流程
服务端编码的基本流程
- 创建TTransport
- 创建TProtocol
- 创建TProcessor
- 创建Server
- 启动服务
客户端编码的基本流程
- 创建TTransport
- 创建TProtocol
- 创建Client
- client方法调用
Transport
Transport层提供了一个简单的网络读写抽象层,这使得thrift底层的transport从系统其他的部分解耦。Thrift使用ServerTransport接口接受或者创建原始transport对象。ServerTransport用在Server端,为到来的连接创建Transport对象。
Protocol
Protocol抽象层定义了一种怎样将内存中数据结构映射成可传输格式的机制。Protocol定义了datatype怎样使用底层的Transport对自己进行编解码
Processor
Processor封装了从输入数据流中读数据和向数据流中写数据的操作。
interface TProcessor {
bool process(TProtocol in, TProtocol out) throws TException
}
与服务相关的processor实现由编译器产生。Processor主要工作流程如下:从连接中读取数据(使用输入protocol),将处理授权给handler(由用户实现),最后将结果写到连接上(使用输出protocol)。
Server
Server将以上所有特性集成在一起
- 创建一个transport对象
- 为transport对象创建输入输出protocol
- 基于输入输出protocol创建processor
- 等待连接请求并将之交给processor处理
Thrift语法
基本类型
thrift不支持无符号类型,因为很多编程语言不存在无符号类型
- byte:有符号字节
- i16:16位有符号整数
- i32:32位有符号整数
- i64:64位有符号整数
- double:64位浮点数
- string:字符串类型
容器类型
集合黄总的元素可以是除了service之外的任何类型
- list<<T>T>:一系列由T类型的数据组成的有序列表,元素可以重复
- set<<T>T>:一系列由T类型的数据组成的无序集合,元素不可重复
- map<K, V>:一个字典结构,key为K类型,value为V类型
其他类型
结构体(struct)
thrift支持struct类型,目的是将一些数据聚合在一起,方便传输管理,struct定义如下
struct People {
1: string name;
2: i32 age;
3: string sex;
}
枚举(enum)
枚举的定义形式和Java的Enum类似,例如:
enum Sex {
RED,
BLUE
}
异常(exception)
thrift支持自定义异常
exception RequestException {
1: i32 code;
2: string reason;
}
服务(Service)
thrift定义的服务相当于Java中创建Interface一样,创建的Service经过代码生成命令后会生成客户端与服务端的框架代码,定义形式如下
service HelloWordService {
// service中定义的函数,相当于Java interface中定义的函数
string doAction(1: string name, 2: i32 age);
}
类型定义
thrift支持类似C++一样的typedef 定义,注意末尾没有逗号或者分号,比如
typedef i32 Integer
typedef i64 Long
常量(const)
thrift使用const关键字定义常量,末尾的分号是可选的,可有可无
const i32 MAX_RETRIES_TIME = 10
命名空间(namespace)
thrift的命名空间相当于Java中的package,主要目的是组织代码。格式为
namespace <语言> <包的位置>
eg:namespace java.com.test.thrift
文件包含
thrift支持文件包含,相当于C/C++中的include,使用关键字include定义
include "global.thrift"
注释
thrift注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都单当做注释,/**/包裹的语句也是注释。
可选与必选
thrift提供两个关键字required,optional,分别用于表示对应的字段时必填的还是可选的。例如:
struct People {
1: required string name;
2: optional i32 age;
}
表示name是必填的,age是可选的。
参考文献
thrift入门教程
Thrift入门初探--thrift安装及java入门实例
Thrift
Thrift RPC实战(一) 初次体验Thrift
【Apache Thrift】windows下thrift的安装(一)
【Apache Thrift】Thrift的使用和编译(二)
Thrift入门初探(2)--thrift基础知识详解
Thrift使用指南
Thrift入门及Java实例演示
个人博客地址:kongdehui.com 欢迎批评指正~~