Osheep

时光不回头,当下最重要。

Thrift入门

Thrift

Thrift简介

什么是thrift

简单来说,是Facebook公布的一款开源跨语言的RPC框架.

什么是RPC框架?

RPC (Remote Procedure Call Protocal),远程过程调用协议

《Thrift入门》

rpc

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入门》

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的使用流程

  1. 首先在一个目录下,创建一个文件,博主是用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); // 可能抛出异常。  
}
  1. 终端进入当前文件夹,在终端输入命令thrift -gen java login.thrift。当前目录下会生成一个gen-java文件夹,文件夹下会按照namespace定义的路径名一层层生成文件夹,到最里层的文件夹里可以看到生成的3个java类Request.java,RequestException.java,LoginService.java
  1. 用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>
  1. 将目录中的代码拷贝到工程的classpath下,IDEA中的classpath就是source所处的文件夹(会有颜色标识,博主的版本是蓝色文件夹)
  2. 创建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();
    }

}
  1. 新建服务器端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();
    }
}
  1. 新建客户端
/**
 * 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();  // 请求结束,断开连接

        }

    }
}
  1. 运行服务器端,控制台输出结果“Starting the simple server…”
  2. 运行客户端,控制台输出结果
    username:lalala
    password:123456

Thrift使用流程

服务端编码的基本流程
  1. 创建TTransport
  2. 创建TProtocol
  3. 创建TProcessor
  4. 创建Server
  5. 启动服务
客户端编码的基本流程
  1. 创建TTransport
  2. 创建TProtocol
  3. 创建Client
  4. 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实例演示

点赞