Thrift 入门教程

简介

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 开发流程,并给出 C++,Java,C#,Python 和 Go开发 Thrfit 应用的实例。

本文的开发环境为: WIN10

Thrift开发概述

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

开发前的准备

  1. 在Thrift官方下载页面下载Thrift编译器,如果希望从源代码编译Thrift编译器,则跳过这一步。本例下载的编译器版本为:thrift-0.10.0.exe
  2. 在Thrift官方下载页面下载Thrift源代码。源代码中包含了编译器的代码和语言相关的库的代码。本例下载的源代码为:thrift-0.10.0.tar.gz 。下载后解压到合适的目录。如果希望从源代码编译Thrift编译器,请参阅Thrift源代码根目录下的README.md。
  3. 构建语言相关的库(如果需要的话)。

开发流程

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

构建语言相关的库

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

构建C++库

本例的编译环境为: WIN10 + VS2015

C++库的源码包含在 lib/cpp 中,在该目录中可以看到两个工程,一个是 libthrift,另一个是 libthriftnb。其中libthriftnb 是非阻塞版本(nb 表示 nonblocking)。这两个工程依赖三个库:BOOST,OpenSSL 和 libevent,因此首先编译这三个库。

编译BOOST库

  1. 下载 BOOST库,本例下载的版本为 1.64.0。下载后解压到合适的目录。由于 Thrift 使用了 BOOST 中的 thread 库,因此需要编译BOOST。

  2. 打开VS2015开发人员命令提示符,切换到 [BOOST解压目录],执行以下命令建立编译环境:

    bootstrap.bat
    
  3. 执行以下命令编译。其中 --toolset 指定了编译工具,由于本例用 VS2015 编译,因此指定为 msvc-14.0--stagedir 指定了生成库的存放路径。

    bjam stage --toolset=msvc-14.0 --without-graph_parallel --stagedir="G:\libs\boost_1_64_0\bin\vc14" link=static runtime-link=shared runtime-link=static threading=multi debug release
    
  4. 喝咖啡。

编译OpenSSL

  1. 下载并安装 Perl,本例下载的版本是:strawberry-perl-5.26.0.1-64bit.msi 。安装后确认 [Perl安装目录]\perl\bin 目录是否在环境变量中,如果不在则手动添加。

  2. 下载并安装 NASM,本例下载的版本是:nasm-2.13.01-installer-x64.exe 。安装后将 [NASM安装目录] 添加到环境变量中。

  3. 下载 OpenSSL 源码并解压,本例下载的版本为:openssl-1.0.1e.tar.gz

  4. 打开VS2015开发人员命令提示符,切换到 [OpenSSL解压目录],执行以下命令进行配置,其中--openssldir指定安装目录,VC-WIN32表示编译成32位的库,如果想要生成64位则改为VC-WIN64A

    perl Configure --openssldir=G:\libs\openssl-1.0.1e VC-WIN32
    

    执行以下命令生成MakeFile:

    ms\do_ms
    

    执行以下命令编译动态库,如果要编译静态库,则将ntdll.mak换成nt.mak:

    nmake -f ms\ntdll.mak
    

    执行以下命令进行安装,安装后会在--openssldir指定的目录中找到编译好的库:

    nmake -f ms\ntdll.mak install
    

编译 libevent

  1. 在libevent官网下载 libevent 源代码并解压,本例下载的版本为:libevent-2.0.22-stable.tar.gz

  2. 打开 VS2015 开发人员命令提示符,切换到解压目录,执行以下命令进行编译:

    nmake Makefile.nmake
    

编译Thrift库

编译好了依赖库,就可以编译Thrift库了。

  1. 进入 [Thrift源码]\lib\cpp 目录,用 VS2015 打开 thrift.sln,如果提示升级编译器和库,则进行升级。

  2. 切换到解决方案页签,展开libthrift工程,server 目录右键 -> 添加 -> 现有项,将 [Thrift源代码目录]\lib\cpp\src\thrift\server\ 目录下的以下四个文件加入到工程中(如果已经在工程中则忽略):

    "TServerFramework.cpp" 
    "TServerFramework.h" 
    "TConnectedClient.cpp" 
    "TConnectedClient.h" 
    
  3. 主菜单中 视图 -> 其他窗口 -> 属性管理器
    双击 3rdparty ,选择 User Macros,将BOOST_ROOT、OPENSSL_ROOT_DIR、 LIBEVENT_ROOT 改成自己的目录。

  4. 切换到解决方案页签,编译 libthrift 和 libthriftnb。编译成功后生成libthrift.lib 和 libthriftnb.lib。这两个库就是在开发时所依赖的库。

##构建Java库

本例构建环境:

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

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

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

ant

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

构建C#库

要求 .NET framework >= 3.5

本例的编译环境为: WIN10 + VS2015

C#库的存放目录为:[Thrift源代码目录]\lib\csharp\src,打开Thrift.sln,编译Thrift项目,编译成功后得到 Thrift.dll。

构建Python库

本例的Python版本为:Python 3.5.3

安装 Python 可以通过源代码中的安装脚本来安装,也可以使用 Python 包管理工具 pip 来安装。

###通过安装脚本安装

Python的库在 [Thrift源代码目录]\lib\py 目录中,该目录中可以找到 setup.py 安装脚本。利用该脚本可以很方便的安装 Python 库。

  1. 以管理员权限打开命令行,切换到 Python 库目录。

  2. 执行以下命令编译Python库。编译后库会生成在当前目录下的build子目录中。

    setup.py build
    
  3. 执行以下命令安装Python库。安装后会在 [Python安装目录]\Lib\site-packages 下生成需要的库: thrift-0.10.0-py3.5-win-amd64.egg 。

    setup.py install
    

通过pip安装

也可以通过 Python 包管理工具 pip 来安装:

pip install thrift

构建Go库

本例的环境为:WIN10 + go1.8.1 windows/amd64

推荐使用 go get 下载最新的 Thrift 库。首先在环境变量里设置好GOPATH,如下图所示。GOPATH的值根据自己的实际情况设置即可。设置好GOPATH之后,使用 go get 工具下载的包就会放在该目录下。

![这里写图片描述](https://img-blog.csdn.net/20170617210338030?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTMwNDk3MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

以管理员权限打开命令提示符(不必切换目录)。利用 go get 下载 Thrift 库,命令如下。下载完毕后就会在 GOPATH 指定的目录找到 Go 的 Thrift 库。

go get git.apache.org/thrift.git/lib/go/thrift/...

使用IDL定义接口

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

##注释

Thrift 支持三种注释:

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

     # 这是一个注释
    
  2. 块注释,用 /**/ 表示。

    /*
    * 这是一个注释
    */
    
  3. 单行注释,用 // 表示。

    // 这是一个注释
    

基本类型

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

常量

常量用const表示:

const i32 MATHPATH = 256

枚举

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

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

结构体

结构体用 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 cpp ICalc.thrift

实例详解

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

C++开发实例

本例环境:WIN10 + VS2015

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

定义接口

新建空白解决方案,命名为 Calc,点击确定。如下图所示:

Thrift 入门教程_第1张图片

解决方案右键,在弹出菜单中选择 [添加] -> [新建项目],如下图所示:

Thrift 入门教程_第2张图片

在添加项目对话框中选择空项目,命名为 Interface,点击确定,如下图所示:

Thrift 入门教程_第3张图片

源文件目录右键,在弹出菜单中选择 [添加] -> [新建项],如下图所示:

Thrift 入门教程_第4张图片

在添加新建项对话框中选择文本文件,命名为 ICalc.thrift,点击添加,如下图所示:

Thrift 入门教程_第5张图片

双击打开 ICalc.thrift,编写如下代码并保存:

I C a l c . t h r i f t ‾ \underline{ICalc.thrift} ICalc.thrift

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

ICalc.thrift 右键,在弹出菜单中选择属性,如下图所示:

Thrift 入门教程_第6张图片

在 ICalc.thrift 属性页中,[配置属性] -> [常规] -> [项类型] 改为 [自定义生成工具],点击[应用],如下图所示:

Thrift 入门教程_第7张图片

还是在 ICalc.thrift 属性页中,[配置属性] -> [自定义生成工具] -> [常规],[命令行]改为 `thrift-0.10.0.exe -r --gen cpp %(FullPath)`,该条命令调用 Thrift 编译器以生成 C++ 代码框架。注意应将 Thrift 编译器所在路径加入到系统环境变量中。`%(FullPath)`表示当前文件(ICalc.thrift)路径。[说明] 是在编译时显示到输出窗口的信息,可以随意填写。 [输出]填写None。[链接对象]改为否。[将输出视为内容]改为否。最后点击确定。如下图所示:

Thrift 入门教程_第8张图片

编译 Interface 项目,编译后会在工程目录下生成 gen-cpp 目录,该目录包含的文件如下。其中 `ICalc.h` 和 `ICalc.cpp` 是接口的实现文件,其中实现了RPC。`ICalc_types.h ICalc_types.cpp ICalc_constants.h ICalc_constants.cpp` 四个文件实现了接口中定义的类型和常量。`ICalc_server.skeleton.cpp`是服务器框架代码,实际开发中可以直接使用这个文件实现服务器业务逻辑。
ICalc.cpp
ICalc.h
ICalc_constants.cpp
ICalc_constants.h
ICalc_server.skeleton.cpp
ICalc_types.cpp
ICalc_types.h

实现服务器

解决方案右键,[添加] -> [新建项目],如下图所示:

Thrift 入门教程_第9张图片

在添加项目对话框中选择 [Win32 控制台应用程序],命名为 Server,点击确定,如下图所示:

Thrift 入门教程_第10张图片

应用程序类型选择 [控制台应用程序],附加选项选择 [空项目],点击完成,如下图所示:

Thrift 入门教程_第11张图片

源文件右键,在弹出菜单中选择 [添加] -> [现有项],如下图所示:

Thrift 入门教程_第12张图片

将上一步生成的文件加入到项目中,如下图所示:

Thrift 入门教程_第13张图片

添加后的项目视图如下图所示:

Thrift 入门教程_第14张图片

[工程属性] -> [配置属性] -> [C/C++] -> [常规] -> [附加包含目录],添加 boost 目录和 C++ 的 Thrift 目录,如下图所示:

Thrift 入门教程_第15张图片

[工程属性] -> [配置属性] -> [链接器] -> [常规] -> [附加库目录],添加 boost 库目录, C++ 的 Thrift 库目录以及 openssl 的库目录,如下图所示:

Thrift 入门教程_第16张图片

[工程属性] -> [配置属性] -> [链接器] -> [输入] -> [附加依赖项],添加依赖库: `libthrift.lib libthriftnb.lib libeay32.lib ssleay32.lib`,如下图所示:

Thrift 入门教程_第17张图片

双击打开 `ICalc_server.skeleton.cpp`,实现 `add` 方法,代码如下:

I C a l c _ s e r v e r . s k e l e t o n . c p p ‾ \underline{ICalc\_server.skeleton.cpp} ICalc_server.skeleton.cpp

int32_t add(const int32_t num1, const int32_t num2) {
	return num1 + num2;
}

Ctrl + A, Ctrl + K, Ctrl + F 格式化代码。

编译工程,生成Server.exe。

在 [OpenSSL目录]\bin 下找到 libeay32.dllssleay32.dll,将这两个 DLL 复制到 Server.exe 同目录下。

实现客户端

解决方案右键,在弹出菜单中选择 [添加] -> [新建项目],如下图所示:

Thrift 入门教程_第18张图片

在添加新项目对话框中,选择 [Win32 控制台应用程序],命名为 Client,点击确定,如下图所示:

Thrift 入门教程_第19张图片

在应用程序设置页面,应用程序类型选择 [控制台应用程序],附加选项选择 [空项目],点击完成,如下图所示:

Thrift 入门教程_第20张图片

源文件右键,在弹出菜单中选择 [添加] -> [现有项],如下图所示:

Thrift 入门教程_第21张图片

选择在第一步生成的 `ICalc.h` 和 `ICalc.cpp`,点击添加,如下图所示:

Thrift 入门教程_第22张图片

源文件右键,在弹出菜单中选择 [添加] -> [新建项],如下图所示:

Thrift 入门教程_第23张图片

选择 [C++ 文件],命名为 main.cpp,点击添加,如下图所示:

Thrift 入门教程_第24张图片

添加后的工程视图如下所示:

Thrift 入门教程_第25张图片

打开 main.cpp,编写以下代码:

m a i n . c p p ‾ \underline{main.cpp} main.cpp

#include 

#include 
#include 
#include 

#include "ICalc.h"

using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;

int main()
{
	boost::shared_ptr socket(new TSocket("localhost", 9090));
	boost::shared_ptr transport(new TBufferedTransport(socket));
	boost::shared_ptr protocol(new TBinaryProtocol(transport));
	ICalcClient client(protocol);

	try
	{
		transport->open();

		int32_t sum = client.add(1, 2);
		std::cout << sum << std::endl;
		
		transport->close();
	}
	catch (TException& tx)
	{
		cout << "ERROR: " << tx.what() << endl;
	}

	system("pause");

	return 0;
}

[工程属性] -> [配置属性] -> [C/C++] -> [常规] -> [附加包含目录],添加 boost 库目录,C++ 的 Thrift 目录以及 Thrift编译器生成的接口的目录。如下图所示:

Thrift 入门教程_第26张图片

[工程属性] -> [配置属性] -> [链接器] -> [常规] -> [附加库目录],添加 C++的 Thrift 库目录,boost 的库目录以及 OpenSSL 的库目录。如下图所示:

Thrift 入门教程_第27张图片

[工程属性] -> [配置属性] -> [链接器] -> [输入] -> [附加依赖项],添加依赖库: `libthrift.lib libthriftnb.lib libeay32.lib ssleay32.lib`,如下图所示:

Thrift 入门教程_第28张图片

编译工程,生成 Client.exe。

在 [OpenSSL目录]\bin 下找到 libeay32.dllssleay32.dll,将这两个 DLL 复制到 Client.exe 同目录下。

测试

运行 Server.exe,再运行 Client.exe,会在 Client.exe 窗口打印 3,如下图所示:

Thrift 入门教程_第29张图片

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 文件,编写接口代码如下。注意包名应该根据自己的实际路径调整。

I C a l c . t h r i f t ‾ \underline{ICalc.thrift } ICalc.thrift

namespace java com.cynhard.thrift.test.iface

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

实现服务器

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

S e r v e r . j a v a ‾ \underline{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,编写客户端代码如下。注意包名应该根据自己的实际路径调整。

C l i e n t . j a v a ‾ \underline{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,编写构建配置如下。注意路径属性需要根据实际路径进行修改。

b u i l d . x m l ‾ \underline{build.xml} build.xml


<project name="calc" basedir="." default="build">

    <property name="thrift.lib.dir" value="G:\libs\thrift-0.10.0\lib\java\build"/>
    <property name="thrift.compiler.dir" value="G:\tools\thrift"/>
    <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">
        <exec executable="${thrift.compiler.dir}\thrift-0.10.0.exe">
            <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.cynhard.thrift.test.server.Server">
            <classpath refid="master-classpath"/>
        java>
    target>
    
    <target name="run_client" description="Run client">
        <java fork="true" failonerror="yes" classname="com.cynhard.thrift.test.client.Client">
            <classpath refid="master-classpath"/>
        java>
    target>
    
project>

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

ant build

编译成功后,服务器和客户端相应的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

C#开发实例

本例环境:WIN10 + VS2015

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

定义接口

新建空白解决方案,命名为 Calc,点击确定。如下图所示:

Thrift 入门教程_第30张图片

解决方案右键,在弹出菜单中选择 [添加] -> [新建项目],如下图所示:

Thrift 入门教程_第31张图片

在添加项目对话框中选择[Visual C++] -> [空项目],命名为 Interface,点击确定,如下图所示。

Thrift 入门教程_第32张图片

源文件目录右键,在弹出菜单中选择 [添加] -> [新建项],如下图所示:

Thrift 入门教程_第33张图片

在添加新建项对话框中选择文本文件,命名为 ICalc.thrift,点击添加,如下图所示:

Thrift 入门教程_第34张图片

双击打开 ICalc.thrift,编写如下代码并保存:

I C a l c . t h r i f t ‾ \underline{ICalc.thrift} ICalc.thrift

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

ICalc.thrift 右键,在弹出菜单中选择属性,如下图所示:

Thrift 入门教程_第35张图片

在 ICalc.thrift 属性页中,[配置属性] -> [常规] -> [项类型] 改为 [自定义生成工具],点击[应用],如下图所示:

Thrift 入门教程_第36张图片

还是在 ICalc.thrift 属性页中,[配置属性] -> [自定义生成工具] -> [常规],[命令行]改为 `thrift-0.10.0.exe -r --gen csharp %(FullPath)`,该条命令调用 Thrift 编译器以生成 C# 代码框架。注意应将 Thrift 编译器所在路径加入到系统环境变量中。`%(FullPath)`表示当前文件(ICalc.thrift)路径。[说明] 是在编译时显示到输出窗口的信息,可以随意填写。 [输出]填写None。[链接对象]改为否。[将输出视为内容]改为否。最后点击确定。如下图所示:

Thrift 入门教程_第37张图片

编译 Interface 项目,编译后会在工程目录下生成 gen-csharp 目录,该目录下只有一个文件 ICalc.cs。该文件即是接口的实现文件,其中实现了RPC。

实现服务器

[解决方案]右键,在弹出菜单中选择 [添加] -> [新建项目],如下图所示:

Thrift 入门教程_第38张图片

在添加项目页面,选择 [Visual C#] -> [Windows] -> [控制台应用程序],命名为 Server。点击确定。如下图所示:

Thrift 入门教程_第39张图片

Server右键,在弹出菜单中选择 [添加] -> [现有项],如下图所示:

Thrift 入门教程_第40张图片

选择生成的接口文件并[添加为链接],如下图所示:

Thrift 入门教程_第41张图片

[引用]右键,在弹出菜单中选择[添加引用],如下图所示:

Thrift 入门教程_第42张图片

选择[浏览],选择 C#的Thrift库,点击添加。如下图所示:

Thrift 入门教程_第43张图片

打开 Program.cs,编写如下代码。注意这里将 Handler类 与服务器类放在了同一个文件中,实际编程时也可以分为两个文件。

P r o g r a m . c s ‾ \underline{Program.cs} Program.cs

using System;

using Thrift.Server;
using Thrift.Transport;

namespace Server
{
    class CalcHandler : ICalc.Iface
    {
        public int add(int num1, int num2)
        {
            return num1 + num2;
        }
    }

    class Program
    {
        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(processor, serverTransport);

                // Use this for a multithreaded server
                // server = new TThreadPoolServer(processor, serverTransport);

                Console.WriteLine("Starting the server...");
                server.Serve();
            }
            catch (Exception x)
            {
                Console.WriteLine(x.StackTrace);
            }
            Console.WriteLine("done.");
        }
    }
}

编译工程,生成Server.exe。

实现客户端

[解决方案]右键,在弹出菜单中选择 [添加] -> [新建项目],如下图所示:

Thrift 入门教程_第44张图片

选择 [Visual C#] -> [Windows] -> [控制台应用程序],命名为Client,点击确定,如下图所示:

Thrift 入门教程_第45张图片

Client右键,在弹出菜单中选择 [添加] -> [现有项],如下图所示:

Thrift 入门教程_第46张图片

选择生成的接口文件并[添加为链接],如下图所示:

Thrift 入门教程_第47张图片

[引用]右键,选择 [添加引用],如下图所示:

Thrift 入门教程_第48张图片

选择[浏览],选择 C#的Thrift库,点击添加。如下图所示:

Thrift 入门教程_第49张图片

打开 Program.cs,编写如下代码:

P r o g r a m . c s ‾ \underline{Program.cs} Program.cs

using System;

using Thrift;
using Thrift.Transport;
using Thrift.Protocol;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                TTransport transport = new TSocket("localhost", 9090);
                TProtocol protocol = new TBinaryProtocol(transport);
                ICalc.Client client = new ICalc.Client(protocol);

                transport.Open();
                try
                {
                    int sum = client.add(1, 2);
                    Console.WriteLine(sum);
                }
                finally
                {
                    transport.Close();
                }
            }
            catch (TApplicationException x)
            {
                Console.WriteLine(x.StackTrace);
            }
        }
    }
}

编译工程,生成 Client.exe。

测试

运行 Server.exe,再运行 Client.exe,会在 Client.exe 窗口打印 3,如下图所示:

Thrift 入门教程_第50张图片

Python开发实例

本例的Python版本为:Python 3.5.3

在开发之前应确保 Python 的 Thrift 包已经安装好,如果还没有安装好,请参见构建Python库。

首先建立如下所示的目录结构。

[somewhere]
	|-calc/
		|-ICalc.thrift  -- 接口文件
		|-Server.py  -- 服务器实现文件
		|-Client.py  -- 客户端实现文件

###定义接口

打开 ICalc.thrift,编写代码如下,注意这里定义了模块名为 calc。

I C a l c . t h r i f t ‾ \underline{ICalc.thrift} ICalc.thrift

namespace py calc

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

以管理员权限打开命令提示符,切换到 calc 目录,执行如下命令生成接口代码。注意因为 Python 包名不能带连字符(-),但是默认生成的文件夹中间是带连字符的(比如gen-py), 因此这里用了 -out 选项指定输出目录。

mkdir genpy
thrift-0.10.0.exe --gen py -out genpy ICalc.thrift

实现服务器

打开 Server.py,编写如下代码:

S e r v e r . p y ‾ \underline{Server.py} Server.py

''' Server '''

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
from genpy.calc.ICalc import Processor

class CalcHandler:
    ''' CalcHandler '''

    def add(self, num1, num2):
        ''' add '''
        return num1 + num2

if __name__ == '__main__':
    HANDLER = CalcHandler()
    PROCESSOR = Processor(HANDLER)
    TRANSPORT = TSocket.TServerSocket('0.0.0.0', port=9090)
    TFACTORY = TTransport.TBufferedTransportFactory()
    PFACTORY = TBinaryProtocol.TBinaryProtocolFactory()
    SERVER = TServer.TSimpleServer(PROCESSOR, TRANSPORT, TFACTORY, PFACTORY)

    print('Starting the server...')
    SERVER.serve()
    print('done.')

###实现客户端

打开 Client.py,编写如下代码:

C l i e n t . p y ‾ \underline{Client.py} Client.py

''' Client '''

from thrift.Thrift import TException
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from genpy.calc.ICalc import Client

if __name__ == '__main__':
    try:
        # Make socket
        TRANSPORT = TSocket.TSocket('localhost', 9090)

        # Buffering is critical. Raw sockets are very slow
        TRANSPORT = TTransport.TBufferedTransport(TRANSPORT)

        # Wrap in a protocol
        PROTOCOL = TBinaryProtocol.TBinaryProtocol(TRANSPORT)

        # Create a client to use the protocol encoder
        CLIENT = Client(PROTOCOL)

        # Connect!
        TRANSPORT.open()

        print(str(CLIENT.add(1, 2)))

        # Close!
        TRANSPORT.close()

    except TException as exceptioin:
        print('%s', exceptioin.message)

测试

首先运行服务器。以管理员身份运行命令提示符,切换到 calc 目录,执行如下命令启动服务器:

python Server.py

运行客户端。以管理员身份运行命令提示符,切换到 calc 目录,执行如下命令启动客户端:

python Client.py

执行结果输出:3

Go开发实例

本例的环境为:WIN10 + go1.8.1 windows/amd64
Go的主要环境变量为:

GOARCH=amd64 
GOPATH=G:\projects\go 
GOROOT=G:\Go

在开发之前应确保 Go 的 Thrift 包已经安装好,如果还没有安装好,请参见构建Go库。

首先建立如下的目录结构。其中的 cynhard.com/tests/thrfit 可以根据实际情况修改。

[GOPATH]/src/
	|-cynhard.com/
		|-tests/
			|-thrift/
				|-iface/
				|	|-ICalc.thrift  -- 接口文件
				|-server/
				|	|-Server.go -- 服务器实现文件
				|-client/
					|-Client.go -- 客户端实现文件

定义接口

打开 ICalc.thrift ,编写如下代码。按照Go的习惯,这里将包名定义为 iface,与目录名保持一致。

I C a l c . t h r i f t ‾ \underline{ICalc.thrift} ICalc.thrift

namespace go iface

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

以管理员权限打开命令提示符,切换到 iface 目录,执行以下命令生成接口代码。注意这里用 -out选项指定生成目录为上级目录,Thrift 编译器在生成代码时,发现上级目录存在 iface 子目录,就把生成的文件放到该目录下。这正是我们想要的目录结构。

thrift-0.10.0.exe --gen go -out .. ICalc.thrift

实现服务器

打开 Server.go,编写如下代码:

S e r v e r . g o ‾ \underline{Server.go} Server.go

package main

import (
	"log"

	"cynhard.com/tests/thrift/iface"
	"git.apache.org/thrift.git/lib/go/thrift"
)

type calcHandler struct{}

func (handler calcHandler) Add(num1 int32, num2 int32) (r int32, err error) {
	r = num1 + num2
	err = nil
	return
}

func main() {
	processor := iface.NewICalcProcessor(calcHandler{})
	transport, err := thrift.NewTServerSocket("localhost:9090")
	if err != nil {
		log.Fatalf("%s\n", err.Error())
	}
	server := thrift.NewTSimpleServer2(processor, transport)
	server.Serve()
}

实现客户端

打开 Client.go,编写如下代码:

C l i e n t . g o ‾ \underline{Client.go} Client.go

package main

import (
	"fmt"
	"log"

	"cynhard.com/tests/thrift/iface"
	"git.apache.org/thrift.git/lib/go/thrift"
)

func main() {
	transport, err := thrift.NewTSocket("localhost:9090")
	if err != nil {
		log.Fatalf("%s\n", err.Error())
	}
	protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
	client := iface.NewICalcClientFactory(transport, protocolFactory)

	transport.Open()
	defer transport.Close()

	sum, err := client.Add(1, 2)
	if err != nil {
		log.Fatalf("%s\n", err.Error())
	}

	fmt.Printf("%d", sum)
}

测试

以管理员权限打开命令提示符,切换到 server 目录下,执行以下命令启动服务器:

go run Server.go

以管理员权限打开命令提示符,切换到 client 目录下,执行以下命令启动客户端:

go run Client.go

可以看到输出结果为:3

总结

  • Thrift是一个用来开发可扩展且跨语言的服务的软件框架。
  • 一个Thrift应用的开发流程为:定义接口->实现服务器->实现客户端。

你可能感兴趣的:(Thrift)