mac下使用 Thrift java

简介

Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。Thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

本文主要介绍 Thrift 开发流程,并给出 Java 开发 Thrfit 应用的实例。

本文的开发环境为: macOS Sierra 10.12.6

Thrift开发概述

Thrift用接口描述语言(Interface description language,IDL)来描述不同编程语言之间的接口。Thrift开发环境包含两个部分:Thrift编译器和语言相关的库。Thrift编译器用来根据IDL生成语言相关的接口代码框架,我们可以使用这个框架很方便的实现客户端和服务器的代码。语言相关的库则封装了不同编程语言之间通信的内部实现,让我们解放双手着重处理业务逻辑。根据编程语言的不同,构建相关库的方法也不同。

开发前的准备

  • Thrift官方下载页面下载Thrift编译器,如果希望从源代码编译Thrift编译器,则跳过这一步。官网只提供了windows下的编译器,thrift-0.10.0.exe,使用非windows跳过此步骤,mac系统请参考Mac下安装Thrift
  • Thrift官方下载页面下载Thrift源代码。源代码中包含了编译器的代码和语言相关的库的代码。本例下载的源代码为:thrift-0.10.0.tar.gz 。下载后解压到合适的目录。如果希望从源代码编译Thrift编译器,请参阅Thrift源代码根目录下的README.md。官网教程 Building from source
  • 构建语言相关的库(如果需要的话)。

Mac下安装Thrift

在mac下安装软件跟Linux安装比较类似,在安装Thrift之前需要先安装依赖。

安装BOOST

下载:http://www.boost.org/ (boost_1_57_0.tar.gz)

拷贝到/usr/local目录下并解压:

tar -zvxf boost_1_57_0.tar.gz

切换目录:

cd boost_1_57_0

执行命令:

./bootstrap.sh —prefix=PATH_TO_BOOST  
sudo ./b2 threading=multi address-model=64 variant=release stage install

./bootstrap.sh该命令用于生成bjam可执行文件,这个东西就是用来编译boost库

安装 libevent

下载:http://libevent.org/ (libevent-2.0.21-stable.tar.gz)

拷贝到/usr/local目录下并解压:

tar -zvxf libevent-2.0.21-stable.tar.gz  

切换目录:

cd libevent-2.0.21-stable    

执行命令:

./configure —-prefix=/usr/local  
make  
sudo make install  

执行make时报如下错误,需要安装openssl; 这是由于mac默认安装了openssl但是没有安装对应的include头文件和lib库,故这里使用homebrew(参考https://brew.sh/index_zh-cn.html)来安装最新的openssl

fatal error: 'openssl/bio.h' file not found

使用homebrew 安装

brew install openssl

安装后,连接到libevent目录下

ln -s /usr/local/Cellar/openssl/1.0.2o_2/include/openssl /usr/local/libevent-2.1.8-stable/include

成功后再执行 make和sudo make install命令就不报错了

安装 Apache Thrift

下载:http://thrift.apache.org/ (thrift-0.8.0.tar.gz)

解压:

tar -zvxf thrift-0.8.0.tar.gz

切换目录:

cd thrift-0.8.0.tar.gz  

编译命令:

./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local

安装命令:

sudo make install

可以查看安装是否成功和版本

thrift -version

注意版本选择:选择Thrift 0.8.0版本,选择高的版本会报bison版本低的错误,在安装0.8.0时也报了一些错误,不过不影响正常使用

最新版0.11.0 在mac中如果用上面步骤安装不成功,可以使用如下方式安装

brew install thrift

开发流程

  • 使用IDL定义服务器和客户端之间的接口。利用Thrift编译器编译该IDL文件,生成语言相关的代码框架。
  • 利用已经生成的代码框架,实现客户端和服务器的业务逻辑。

构建语言相关的库

语言相关的库代码在Thrift源代码的lib下,每种语言一个目录,比如C++就在cpp目录下。在每种语言的目录下,都有一个README.md用来描述该语言库的构建和使用方法。语言相关的库根据编程语言的不同,构建的方式也不同。如果是编译型语言的话,则需要编译。本篇只提供java案例

构建Java库

本例构建环境:

java version "1.8.0_131"
Apache Ant(TM) version 1.10.5

在编译之前请确保正确安装和配置了 JDK 和 Ant。

Java库源代码目录为:[Thrift源代码目录]\lib\java。Java库需要使用Ant 来编译。管理员方式打开命令行,切换到Java库元源代码目录,执行:

ant

编译后会在 build 子目录下找到 libthrift-0.10.0.jar,这就是我们需要的开发包。在 build/lib 下是 libthrift-0.10.0.jar 的依赖包。

使用IDL定义接口

Thrift 中 IDL 文件以 .thrift 为后缀。该文件用来描述服务器与客户端之间的接口。在编写 thrift 文件之前,还需要了解一下用以描述接口的语法。

注释

Thrift 支持三种注释:

脚本注释,用 # 表示,例如:

#这是一个注释

块注释,用 // 表示。

/*

  • 这是一个注释

    */

单行注释,用 // 表示。

// 这是一个注释

基本类型

类型 描述
bool 布尔类型,1byte
i8 有符号整形,8bits,即byte
i16 有符号整型,16bits
i32 有符号整型,32bits
i64 有符号整型,64bits
double 浮点型,64bits
string 字符串
binary Blob,byte数组
map<t1,t2> map
list 有序列表
set 无重复元素的容器

常量

常量用const表示:

const i32 MATHPATH = 256

枚举

用 enum 定义枚举。枚举类型是32位的整数。值是可选的,从1开始。

enum Operation {
    ADD = 1,
    SUBTRACT = 2,
    MULTIPLY = 3,
    DIVIDE = 4
}

结构体

即java中的bean, 结构体用 struct 表示,是基本的复合数据类型,由若干字段组成。每个字段由整型ID,类型,名称和可选的默认值组成。字段可以声明为 optional,表示如果没有被设置,则不进行序列化。

struct Work {
    1: i32 num1 = 0,
    2: i32 num2,
    3: Operation op,
    4: optional string comment,
}

结构体也可以定义为异常:

exception InvalidOperation {
    1: i32 whatOp,
    2: string why
}

服务

服务类似 class, 用 service 关键字定义。服务可以用 extends 关键字继承其他服务。service 由一系列方法组成。方法由返回值,参数列表和一个可选的异常列表组成。参数列表和异常列表的语法和结构体的语法一样。

service Calculator extends shared.SharedService {

   void ping(),

   i32 add(1:i32 num1, 2:i32 num2),

   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),

   /**
    * oneway 表示客户端发送请求后不需要响应。onway 方法返回值必须是void 
    */
   oneway void zip()

}

include 指令

用 include 指令包含其他 thrift 文件:

include "another.thrift"

namespace 指令

namespace 指令指定语言相关的名称空间:

namespace cpp nscpp # 指定C++的名称空间为 nscpp
namespace java nsjava # 指定Java的包为 nsjava

typedef 指令

typedef 指令用来指定类型别名,和 C 一样。

typedef i32 MyInteger

编译IDL文件

Thrift 编译器的用法如下:

thrift [options] file

可以通过 –help 选项来了解具体用法:

thrift --help

通常用以下命令将IDL编译成语言相关的接口代码,-r(recurse的首字母)表示递归生成被包含的文件,–gen后面接生成的语言。

thrift-0.10.0.exe -r --gen java ICalc.thrift

实例详解

下面分别给出 JAVA开发 Thrift 应用的实例。需要注意的是某种语言开发的服务器和任何语言开发的客户端都可以实现互联,这正是 Thrift 的特性之一。

Java开发实例

本例开发环境:

java version "1.8.0_131"
Apache Ant(TM) version 1.10.1

在开发之前应确保 Java 的 Thrift 包已经编译好,如果还没有编译好,请参见构建Java库

首先建立如下所示的目录结构。其中 com/cynhard/thrift/test 是包的路径,可以根据自己的域名进行调整。iface, server, client三个目录分别用来存放接口,服务器,客户端的代码。build.xml是Ant构建文件。

[somewhere]
    |-calc/
        |-com/
        |   |-cynhard/
        |       |-thrift/
        |           |-test/
        |               |-iface/  -- 接口文件目录
        |               |   |-ICalc.thrift  -- 接口文件
        |               |-server/ -- 服务器目录
        |               |   |-Server.java  -- 服务器实现文件
        |               |-client/ -- 客户端目录
        |                   |-Client.java  -- 客户端实现文件
        |-build.xml  -- ant 文件

定义接口

打开 ICalc.thrift 文件,编写接口代码如下。注意包名应该根据自己的实际路径调整。

ICalc.thrift

namespace java com.cynhard.thrift.test.iface

service ICalc {
    i32 add(1:i32 num1, 2:i32 num2),
}

实现服务器

打开 Server.java,编写服务器代码如下。注意包名应该根据自己的实际路径调整。

Server.java
package com.cynhard.thrift.test.server;

import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TServer.Args;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import com.cynhard.thrift.test.iface.ICalc;

public class Server {

    static class CalcHandler implements ICalc.Iface {
        @Override
        public int add(int num1, int num2) throws TException {
            return num1 + num2;
        }
    }

    public static void main(String[] args) {
        try {
            CalcHandler handler = new CalcHandler();
            ICalc.Processor processor = new ICalc.Processor(handler);
            TServerTransport serverTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));

            System.out.println("Starting the server...");
            server.serve(); 
       } catch (Exception e) { 
           e.printStackTrace(); 
       } 
    }
}

实现客户端

打开 Client.java,编写客户端代码如下。注意包名应该根据自己的实际路径调整。

Client.java

package com.cynhard.thrift.test.client;

import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import com.cynhard.thrift.test.iface.ICalc;

public class Client {
    public static void main(String[] args) {
        try {
            TTransport transport = new TSocket("localhost", 9090);
            transport.open();

            TProtocol protocol = new TBinaryProtocol(transport);
            ICalc.Client client = new ICalc.Client(protocol);
            System.out.println(client.add(1, 2));

            transport.close();
        } catch (TException e) {
            e.printStackTrace();
        }
    }
}

构建工程

打开 build.xml,编写构建配置如下。注意路径属性需要根据实际路径进行修改。

build.xml

<?xml version="1.0"?>
<project name="calc" basedir="." default="build">

<property name="thrift.lib.dir" value="/Users/zhouguangsheng/project/study/Thrift/thrift-0.11.0/lib/java/build"/>
<property name="thrift.compiler.dir" value="/Users/zhouguangsheng/project/study/Thrift/thrift-0.11.0/lib/java/build"/>
<property name="interface.dir" value="${basedir}\com\cynhard\thrift\test\iface"/>

<path id="master-classpath">
    <fileset dir="${thrift.lib.dir}">
        <include name="**\*.jar"/>
    </fileset>
    <pathelement path="."/>
</path>

<target name="build" description="Compile Calc">
    <!-- 如果单独编译thrift文件,注释下面exec这段 -->
    <exec executable="${thrift.compiler.dir}/libthrift-0.11.0.jar">
        <arg line="--gen java -out ${basedir} ${interface.dir}\ICalc.thrift"/>
    </exec>
    <javac>
        <src path="."/>
        <classpath refid="master-classpath"/>
    </javac>
</target>

<target name="run_server" description="Run server">
    <java fork="true" failonerror="yes" classname="com.zgs.thrift.test.server.Server">
        <classpath refid="master-classpath"/>
    </java>
</target>

<target name="run_client" description="Run client">
    <java fork="true" failonerror="yes" classname="com.zgs.thrift.test.client.Client">
        <classpath refid="master-classpath"/>
    </java>
</target>

</project>

上面的工作完成后,就可以开始构建工程了。以管理员权限打开命令提示符,切换到 项目根目录。执行以下命令编译工程:

ant build

直接编译可能会不成功,thrift文件也可以单独编译,ant build.xml文件中移除,只编译java

thrift -gen java -out /Users/zhouguangsheng/project/study/Thrift/thriftDemo/ /Users/zhouguangsheng/project/study/Thrift/thriftDemo/com/zgs/thrift/test/iface/ICalc.thrift

-out参数为编译的java文件要存放的路径

编译成功后,服务器和客户端相应的class文件就生成在了相应的目录中。下面就可以开始测试了。

测试

执行以下命令运行服务器。

ant run_server

输出结果如下,可以看到输出了 Starting the server…,表明服务器已经启动。

Buildfile: G:\projects\java\calc\build.xml

run_server:
     [java] Starting the server...

因为服务器在一个单独的窗口运行,为了测试客户端,需要打开另一个命令提示符,同样切换到 calc 目录。执行以下命令运行客户端:

ant run_client

输出结果如下,可以看到返回了正确的结果:3 。

Buildfile: G:\projects\java\calc\build.xml

run_client:
     [java] Received 1
     [java] 3

BUILD SUCCESSFUL
Total time: 0 seconds