# 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>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/web-zhong-jian-jian-xue-xi/thrift-jie-shao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
