> For the complete documentation index, see [llms.txt](https://jun-wang.gitbook.io/learnjava/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/web-zhong-jian-jian-xue-xi/thrift-jie-shao.md).

# Thrift介绍

由于我们组要求使用的是Thrift0.8.0版本，下面以这个版本为例来讲解如何安装配置Thrift。

## 一、安装

下载地址：<http://archive.apache.org/dist/thrift/0.8.0/thrift-0.8.0.tar.gz>

```
tar -zxvf thrift-0.8.0.tar.gz
cd thrift-0.8.0
./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local --without-csharp --without-erlang --without-go --without-haskell --without-ruby --without-cpp --without-perl --without-php --without-php_extension --without-python
make
sudo make install
# 检查是否安装成功，出现下面的情况说明已经正确安装
thrift -version
Thrift version 0.8.0
```

## 二、Thrift简介

Thrift是一个轻量级的、跨语言的远程服务调用框架。Thrift支持不同的编程语言，包括C++、Java、Python、PHP、Ruby等。

## 三、Thrift软件栈

Thrift软件栈从下到上依次是：传输层、协议层、处理层和服务层。

* 传输层：**TTransport**，负责直接从网络读取和写入数据，定义了具体的网络传输协议，比如TCP/IP等；
* 协议层：**TProtocal**，定义了数据传输格式，负责网络传输数据的序列化和反序列化，比如说json、XML、二进制数据等；
* 处理层：**TProcessor**，处理层是由具体的IDL（接口描述语言）生成的，封装了具体的底层网络传输和序列化方式，并委托给用户实现的handler进行处理；
* 服务层：**TServer**，整合上述组件，提供具体的网络线程/IO模型，形成最终的服务，然后接收Client的请求，并转到某个TProcessor上进行请求处理。

**Thrift的传输层**

创建的传输层有以下几种：

1. TSocket：使用阻塞式IO进行传输，是最常见的模式；
2. TNonblockingTransport：使用**非阻塞方式**，用于构建**异步客户端**
3. TFramedTransport：使用**非阻塞方式**，按**块的大小**进行传输，类似于`Java`中的`NIO`。
4. TBufferedTransport：对某个Transport对象操作的数据进行buffer，即从buffer中读取数据进行传输，或者将数据直接写入buffer。
5. THttpTransport：采用Http传输协议进行数据传输。
6. TFileTransport：文件（日志）传输类，允许client将文件传给server，允许server将收到的数据写到文件中。&#x20;
7. TZlibTransport：与其他的TTransport配合使用，压缩后对数据进行传输，或者将收到的数据解压&#x20;

**Thrift的服务端类型**

1. TSimpleServer：**单线程**服务器端，使用标准的**阻塞式**`I/O`，只在演示的时候用，实际工作中很少用到；
2. TThreadPoolServer：**多线程**服务器端，使用标准的**阻塞式**`I/O`，主线程负责监听新连接，其他处理交给单独的线程池；
3. TNonblockingServer：**单线程**服务器端，使用**非阻塞式**`I/O`，一个线程循环处理监听socket，处理连接，读，写请求；
4. THsHaServer：**半同步半异步**服务器端，基于**非阻塞式**`IO`读写和**多线程**工作任务处理，对TNonblockingServer的优化，读请求以及后续的业务处理交给单独的线程池来做；
5. TThreadedSelectorServer：**多线程选择器**服务器端，对`THsHaServer`在**异步**`IO`模型上进行增强，最高级的模式，大部分场景下性能都不会差，如果不知道选哪个就选这个。

## 四、Thrift数据类型

Thrift主要有下面5种类型：

**1. 基本类型：**

* **bool**: 布尔值
* **byte**: 8位有符号整数
* **i16**: 16位有符号整数
* **i32**: 32位有符号整数
* **i64**: 64位有符号整数
* **double**: 64位浮点数
* **string**: UTF-8编码的字符串
* **binary**: 二进制串

> 注意：Thrift不支持无符号整型，因为很多编程语言本身就不存在无符号整型，如java。

**2. 结构体类型：**

* **struct**: 定义的结构体对象

**3. 容器类型：**

* **list**: 有序元素列表
* **set**: 无序无重复元素集合
* **map**: 有序的key/value集合

**4. 异常类型：**

* **exception**: 异常类型

**5. 服务类型：**

* **service**: 具体对应服务的类

此外thrift还支持常量和枚举。

## 五、Thrift实战

### 5.1 编写IDL文件

hello.thrift

```
service HelloWorldService {
  string say(1: string username)
}
```

### 5.2 编译IDL文件生成Java文件

```
thrift -gen java hello.thrift
```

于未指定代码生成的目标目录，生成的类文件默认存放在`gen-java`目录下。这里会生成4个重要的内部接口/类：

* **Iface**：**服务端**通过实现`HelloWorldService.Iface`接口，向**客户端**的提供具体的**同步**业务逻辑。
* **AsyncIface**：**服务端**通过实现`HelloWorldService.Iface`接口，向**客户端**的提供具体的**异步**业务逻辑。
* **Client**：**客户端**通过`HelloWorldService.Client`的实例对象，以**同步**的方式**访问服务端**提供的服务方法。
* **AsyncClient**：**客户端**通过`HelloWorldService.AsyncClient`的实例对象，以**异步**的方式**访问服务端**提供的服务方法。

### 5.3 代码实现

新建maven工程，pom中添加依赖

```markup
<dependencies>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.6.6</version>
  </dependency>
  <dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.8.0</version>
  </dependency>
</dependencies>
```

将生成的`HelloWorldService.java`源文件拷贝进项目源文件目录中，并实现`HelloWorldService.Iface`的定义的`say()`方法。

HelloWorldServiceImpl.java

```java
import org.apache.thrift.TException;

public class HelloWorldServiceImpl implements HelloWorldService.Iface {
    @Override
    public String say(String username) throws TException {
        return "Hello " + username;
    }
}
```

服务端实现：SimpleServer.java

```java
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 java.net.ServerSocket;

public class SimpleServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        TServerSocket serverTransport = new TServerSocket(serverSocket);
        HelloWorldService.Processor processor =
                new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());

        TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
        TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
        tArgs.processor(processor);
        tArgs.protocolFactory(protocolFactory);

        // 简单的单线程服务模型 一般用于测试
        TServer tServer = new TSimpleServer(tArgs);
        System.out.println("Running Simple Server");
        tServer.serve();
    }
}
```

客户端实现：SimpleClient.java

```java
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;

public class SimpleClient {
    public static void main(String[] args) {
        TTransport transport = null;
        try {
            transport = new TSocket("127.0.0.1" , 9999, 5000);
            TProtocol protocol = new TBinaryProtocol(transport);
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);
            transport.open();

            String result = client.say("nik");
            System.out.println("Result =: " + result);
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}
```

依次运行服务端和客户端会出现以下结果：

```
Running Simple Server

Result =: Hello nik
```

### 5.4 总结

从使用thrift的过程中可以总结thrift实现服务端和客户端逻辑的基本思路。

Thrift实现服务端：

* 实现服务处理接口impl
* 创建TProcessor
* 创建TServerTransport
* 创建TProtocol
* 创建TServer
* 启动Server

Thrift实现客户端：

* 创建Transport
* 创建TProtocol
* 基于TTransport和TProtocol创建Client

## 六、遇到的问题

**1. 运行时报错`Class JavaLaunchHelper is implemented in both...`**

【错误描述】

idea run服务端的时候报错：objc\[79886]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.7.0\_80.jdk/Contents/Home/bin/java (0x106b914c0) and /Library/Java/JavaVirtualMachines/jdk1.7.0\_80.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x108e7e4e0). One of the two will be used. Which one is undefined.

【问题解决】

这是Mac版idea的一个bug，jdk1.8及以上已经修复了，因为项目要求是jdk1.7，并且这个错误不影响正常使用可以忽略不计了。

**2. 启动server时报错`SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".`**

【错误描述】

启动server的时候报错：

```
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
```

【问题解决】

上面的意思是，在运行时，你没有做日志的实现（或者说日志的绑定），所以slf4j简简单单的使用了一个什么也不会做的空实现。 为了看到正确的输出，你应该尝试使用一个简单（simple）的实现，这个实现根本不需要任何配置！只要回到pom.xml然后添加如下配置：

```
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.6.6</version>
</dependency>
```

> 参考：
>
> 【thrift概述与入门】<https://juejin.im/post/6844903622380093447>
>
> 【thrift基本原理】<https://www.jianshu.com/p/5e3a4ab838a3>
