peace唠叨

Apache Thrift入门学习

本文简单介绍下Apache Thrift。thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。

全部代码下载:Github链接:github链接,点击惊喜;写文章不易,欢迎大家采我的文章,以及给出有用的评论,当然大家也可以关注一下我的github;多谢;

1.Thrift介绍

Thrift是一种接口描述语言和二进制通讯协议,[1]它被用来定义和创建跨语言的服务。[2]它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统[3])、Cappuccino、[4]Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。[5]虽然它以前是由Facebook开发的,但它现在是Apache软件基金会的开源项目了。该实现被描述在2007年4月的一篇由Facebook发表的技术论文中,该论文现由Apache掌管(摘自维基百科)

2.Thrift安装

安装Thrift主要是为了通过IDL文件生成需要的接口和类。
下载地址:http://archive.apache.org/dist/thrift/0.9.3/

2.1windowx的安装:

  1. 下载thrift-0.9.3.exe
  2. 新建一个文件夹如:Thtift
    3.将thrift-0.9.3.exe该名为thrift(可以不改名)
    4.修改系统的环境变量:
    00
  3. 测试安装成功否:
    01

    2.2Linux的安装:

  4. 下载:thrift-0.9.3.tar.gz
    2.tar -xzf thrift-0.9.3.tar.gz
    3.cd thrift-0.9.3
  5. ./configure
  6. make 有点慢
    6.sudo make install

3.Thrift的IDL格式要求

IDL即接口定义语言,主要是为了生成各种语言(例如:java,c/C++等)能够理解的消息结构、接口定义的描述形式。因为要生成其他语言的类,接口,或者结构体,所以必须遵守一些规范。

3.1文件支持的基本类型:

  • bool: 布尔值
  • byte: 有符号字节
  • i16: 16位有符号整型
  • i32: 32位有符号整型
  • i64: 64位有符号整型
  • double: 64位浮点型
  • string: 字符串/字符数组
  • binary: 二进制数据

    3.2容器类型:

  • list: 一系列由T类型的数据组成的有序列表,元素可以重复
  • set: 一系列由T类型的数据组成的无序集合,元素不可重复
  • map: 一个字典结构,key为K类型,value为V类型,相当于Java中的HashMap

    3.3结构体:

    结构体经过解析后在面向对象语言中,表现为“类定义”;在弱类型语言、动态语言中,表现为“结构/结构体”。
    定义格式如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct <结构体名称> {
    <序号>:[字段性质] <字段类型> <字段名称> [= <默认值>] [;|,]
    }
    ·结构体名称:可以按照您的业务需求,给定不同的名称(区分大小写)。但是要注意,一组IDL定义文件中结构体名称不能重复,且不能使用IDL已经占用的关键字(例如required 、struct 等单词)。

    ·序号:序号非常重要。正整数,按照顺序排列使用。这个属性在Apache Thrift进行序列化的时候被使用。

    ·字段性质:包括两种关键字:required 和 optional,如果您不指定,那么系统会默认为required。required表示这个字段必须有值,并且Apache Thrift在进行序列化时,这个字段都会被序列化;optional表示这个字段不一定有值,且Apache Thrift在进行序列化时,这个字段只有有值的情况下才会被序列化。

    ·字段类型:在struct中,字段类型可以是某一个基础类型,也可以是某一个之前定义好的struct,还可以是某种Apache Thrift支持的容器(setmaplist),还可以是定义好的枚举。字段的类型是必须指定的。

    ·字段名称:字段名称区分大小写,不能重复,且不能使用IDL已经占用的关键字(例如required 、struct 等单词)。

    ·默认值:您可以为某一个字段指定默认值(也可以不指定)。

    ·结束符:在struct中,支持两种结束符,您可以使用“;”或者“,”。当然您也可以不使用结束符(Apache Thrift代码生成程序,会自己识别到)

3.4枚举:

枚举的定义形式和C的Enum定义差不多

1
2
3
enum <枚举名称> {
<枚举字段名> = <枚举值>[;|,]
}

3.5常量定义:

IDL允许定义常量。常量的关键字为“const”,与C语言类似,常量可以是基础类型,也可以是定义的Struct。
如:

i32 MAX_AREA = 60 ```
1
###3.6异常定义:
IDL允许定义异常,在定义服务接口使用。其定义方法类似于上面的Struct。因为exception也是类,定义时只需要将struct换成exception就行,如下:

exception <异常名称> {
<序号>:[字段性质] <字段类型> <字段名称> [= <默认值>] [;|,]
}

1
2
###3.7服务接口:
服务接口在生成代码过程中,生成的接口类是我们需要实现的提供给客户端调用的。service服务接口定义如下:

service <服务名称> {
<服务方法名>([<入参序号>:[required | optional] <参数类型> <参数名> …]) [throws ([<异常序号>:[required | optional] <异常类型> <异常参数名>…])]
}
·服务名称:服务名可以按照您的业务需求自行制定,注意服务名是区分大小写的。IDL中服务名称只有两个限制,就是不能重复使用相同的名称,不能使用IDL已经占用的关键字(例如required 、struct 等单词)。

·返回值类型:如果这个调用方法没有返回类型,那么可以关键字“void”; 可以是Apache Thrift的基础类型,也可以是某一个之前定义好的struct,还可以是某种Apache Thrift支持的容器(set、map、list),还可以是定义好的枚举。

·服务方法名:服务方法名可以根据您的业务需求自定制定,注意区分大小写。在同一个服务中,不能重复使用一个服务方法名命名多个方法(一定要注意),不能使用IDL已经占用的关键字。

·服务方法参数:<入参序号>:[required | optional] <参数类型> <参数名>。注意和struct中的字段定义相似,可以指定required或者optional;如果不指定则系统默认为required 。如果一个服务方法中有多个参数名,那么这些参数名称不能重复。

·服务方法异常:throws ([<异常序号>:[required | optional] <异常类型> <异常参数名>。throws关键字是服务方法异常定义的开始点。在throws关键字后面,可以定义1个或者多个不同的异常类型。

1
2
###3.8命名空间:
thrift的命名空间相当于Java中的package的意思,主要目的是组织代码。thrift使用关键字namespace定义命名空间,例如:

namespace java cn.wpeace.thrift

1
2
###3.9其他特性:
1.注释:

支持下面三种方式:
/*

  • 注释方式1:
    **/// 注释方式2# 注释方式3
    1
    2
    2. IDL文件包含
    thrift也支持文件包含,相当于C/C++中的include,Java中的import。使用关键字include定义,例 如:

include “wpeace.thrift”

1
2
3
4
5
6
7
##4.Hello world Thrift
下面讲解下编写一个Hello world工程的步骤:
1. 编写IDL文件,以及编译
2.下载jar包建立服务端代码:实现接口,和启动服务
3.建立客户端代码进行访问
###4.1编写IDL文件和编译
1. 按照第三节的规范,编写IDL文件。如下:我命名为hello.thrift

//命名空间定义:java包
namespace java cn.wpeace.thrift
//结构体定义:转化java中的实体类
struct Request{
1:required string userName;
2:required string password;
}
//定义返回类型
struct Student{
1:required string naem;
2:required i32 age;
}
//异常描述定义
exception HelloException{
1:required string msg;
}
//服务定义,生成接口用
service StudentService{
list getAllStudent(1:Request request)throws (1:HelloException e);
}

1
2
3
4
2. 打开终端输入命令:thrift -gen java ./hello.thrift 后会在当前目录生成gen-java文件夹,里面包含了我们定义的类和接口。按namespace路径存放。
![04](http://picture.wpeace.cn/thrift04.jpg)
###4.2下载jar包建立服务端代码
1.需要下载的jar包:

org.slf4j.api.jar
org.slf4j.simple.jar
libthrift-0.9.1.jar

1
2
[点击下载](http://download.csdn.net/detail/peace1213/9628254)
2.建立服务端代码,需要实现StudnetService类中的Iface接口,代码如下:详见注释

package cn.wpeace.thrift;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.server.TThreadPoolServer.Args;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import cn.wpeace.thrift.StudentService.Iface;
import cn.wpeace.thrift.StudentService.Processor;
public class StudentServiceImpl implements Iface {// 实现的是StudentService类下面的接口
@Override
public List getAllStudent(Request request) throws HelloException, TException {
System.out.println(request.getUserName());
System.out.println(request.getPassword());
List students = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Student student = new Student();
student.setNaem(“peace” + i);
student.setAge(22 + i);
students.add(student);
}
return students;
}
}

1
3.启动服务器:

public static void main(String[] args) {
try {
System.out.println(“服务启动”);
// 非阻塞式
TNonblockingServerSocket serverSocket=new TNonblockingServerSocket(8081);
// 服务执行控制器,类似于rmi中的bind
Processor processor = new StudentService.Processor(new StudentServiceImpl());
// 为服务器设置对应的IO网络模型
TNonblockingServer.Args tArgs = new TNonblockingServer.Args(serverSocket);
// 设置控制器
tArgs.processor(processor);
//设置传输方式
// tArgs.transportFactory(new TFramedTransport.Factory());
// 设置消息封装格式
tArgs.protocolFactory(new TBinaryProtocol.Factory());//Thrift特有的一种二进制描述格式
// 启动Thrift服务
TNonblockingServer server = new TNonblockingServer(tArgs);
server.serve();//启动后,程序就停在这里了。
System.out.println(“服务结束”);
} catch (TTransportException e) {
e.printStackTrace();
}
}
@Test
public void test(String[] args) {
try {
System.out.println(“服务启动”);
// 阻塞式同步socket
TServerSocket serverSocket = new TServerSocket(8081);
// 服务执行控制器,类似于rmi中的bind
Processor processor = new StudentService.Processor(new StudentServiceImpl());
// 为服务器设置对应的IO网络模型
Args tArgs = new Args(serverSocket);
// 设置控制器
tArgs.processor(processor);
// 设置消息封装格式
tArgs.protocolFactory(new TBinaryProtocol.Factory());//Thrift特有的一种二进制描述格式
// 设置线程池参数
tArgs.executorService(Executors.newFixedThreadPool(10));//线程池调度器,由于是阻塞模式需要设置线程池。
// 启动Thrift服务
TThreadPoolServer server = new TThreadPoolServer(tArgs);
server.serve();//启动后,程序就停在这里了。
System.out.println(“服务结束”);
} catch (TTransportException e) {
e.printStackTrace();
}
}

1
2
###4.3建立客户段代码:
注意:客户端和服务端的传输协议需要一至,同时阻塞和非阻塞传输也有区别。详细见代码

public static void main(String[] args) {
try {
//建立socket连接
//TSocket tSocket = new TSocket(“192.168.1.118”,8081);
//如果是非阻塞型 需要使用
TTransport tSocket = new TFramedTransport(new TSocket(“192.168.1.118”,8081, 30000));
//设置封装协议
TBinaryProtocol protocol = new TBinaryProtocol(tSocket);
//建立调用client
StudentService.Client client=new StudentService.Client(protocol);
//设置调用参数:
Request request=new Request().setUserName(“peace”).setPassword(“123456”);
//准备传输
tSocket.open();
//正式调用接口
List allStudent = client.getAllStudent(request);
//请求结束,断开连接
tSocket.close();
for(Student student:allStudent)

        {
            System.out.println(student.getNaem()+":"+student.getAge());
        }

    } catch (TTransportException e) {
        e.printStackTrace();
    } catch (HelloException e) {
        e.printStackTrace();
    } catch (TException e) {
        e.printStackTrace();
    }

}

```

4.4测试:

1.启动服务器
2.启动客户端
客户端结果:
02
服务端结果:
03

本文来自伊豚(blog.wpeace.cn)

Peace wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!