Apache Thrift 指南

Thrift 安装

Mac 下 Thrift 配置

快速安装

使用 Mac 的包管理器 Homebrew。首先安装 Homebrew:

1
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

使用 Homebrew 安装 thrift:

1
brew install thrift

如需手动安装特定版本,请参考以下若干小节。

手动安装

首先编译安装 Thrift 的依赖 Boost 和 libevent,接着编译安装 Thrift。

安装 Boost

boost.org 下载 boost 库,解压进入文件夹,并通过以下命令编译:

1
2
./bootstrap.sh
sudo ./b2 threading=multi address-model=64 variant=release stage install

Boost 库是对 C++ 标准库提供扩展的一些 C++ 程序库的总称。

安装 libevent

libevent.org 下载 libevent, 解压并通过如下命令编译安装:

1
2
3
./configure --prefix=/usr/local
make
sudo make install

libevent 是用 C 语言编写的、轻量级开源高性能网络库,基于事件驱动且支持跨平台。

安装 Apache Thrift

下载最新版本的 Apache Thrift,解压并通过如下命令编译安装:

1
2
3
4
./configure --prefix=/usr/local/ \
--with-boost=/usr/local \
--with-libevent=/usr/local \
--without-ruby ## 不需要的语言可以通过 --without-[language] 去掉

运行时可能会报错:Bison version 2.5 or higher must be installed on the system!。参考 How to install bison on mac OSX 解决:

1
2
brew install bison
brew link bison --force

brew link bison 是在系统路径下添加 bison 的 symlinks。在装完 thrift 之后记得:

1
brew unlink bison

Ubuntu 下 Thrift 配置

参考链接

[1] Debian/Ubuntu install
[2] Building from source
[3] Apache Thrift Tutorial

安装依赖

执行命令:

1
sudo apt-get install automake bison flex g++ git libboost1.55-all-dev libevent-dev libssl-dev libtool make pkg-config

安装 Thrift

Thrift 官网 下载最新的打包文件,解压后进入文件夹,输入如下命令编译安装:

1
./configure && make && sudo make install

Thrift C++ 实例

本节中我们实现一个简单的加减乘除计算器 C/S 实例。

基本过程

  1. 编写接口描述文件 calculator.thrift,定义数据类型和服务接口
  2. 编译 calculator.thrift 生成 gen-cpp 源代码文件夹
  3. 编写 server 代码,引用 gen-cpp 与 thrift 库目录编译
  4. 编写 client 代码,引用 gen-cpp 与 thrift 库目录编译
  5. 运行 ./server
  6. 运行 ./client

Thrift 脚本

假设服务端(Server)为客户端(Client)提供简单的数学运算功能,编写 calculator.thrift 脚本文件如下:

1
2
3
4
5
6
7
8
9
namespace cpp MathServer
service MathService{
i32 add (1:i32 a, 2:i32 b),
i32 sub (1:i32 a, 2:i32 b),
i32 mul (1:i32 a, 2:i32 b),
i32 div (1:i32 a, 2:i32 b),
i32 mod (1:i32 a, 2:i32 b)
}

编译 Thrift 脚本的基本命令格式为:

1
thrift --gen <language> <Thrift filename>

如果在一个 .thrift 文件里包含了其他的 .thrift 文件,需要递归编译,执行:

1
thrift --gen -r <language> <Thrift filename>

本例中,我们执行如下命令编译:

1
thrift --gen cpp calculator.thrift

在当前目录下会生成一个 gen-cpp 文件夹。文件夹里包含 MathService.h/cpp, calculator_constants.h/cpp, calculator_types.h/cpp 和 MathService_server.skeleton.cpp 7 个文件。前 6 个文件用于接口定义,需要同时被 Client 和 Server 代码引用(调用或实现),而 MathService_server.skeleton.cpp 则提供了 Server 端代码的一个基本框架。

Server 程序

新建 calculator 文件夹,把 gen-cpp 复制进去,并将 gen-cpp 里的 MathService_server.skeleton.cpp 重命名为 server.cpp,移动到 server 文件夹。实现 server.cpp 后的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "gen-cpp/MathService.h"
#include <iostream>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using boost::shared_ptr;
using namespace ::MathServer;
class MathServiceHandler : virtual public MathServiceIf {
public:
MathServiceHandler() {
// Your initialization goes here
}
int32_t add(const int32_t a, const int32_t b) {
return a + b;
}
int32_t sub(const int32_t a, const int32_t b) {
return a - b;
}
int32_t mul(const int32_t a, const int32_t b) {
return a * b;
}
int32_t div(const int32_t a, const int32_t b) {
return a / b;
}
int32_t mod(const int32_t a, const int32_t b) {
return a % b;
}
};
int main(int argc, char** argv) {
int port = 9090;
shared_ptr<MathServiceHandler> handler(new MathServiceHandler());
shared_ptr<TProcessor> processor(new MathServiceProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
std::cout << "Start server..." << std::endl;
server.serve();
return 0;
}

Client 程序

在 calculator 文件夹里新建 client.cpp 文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include "gen-cpp/MathService.h"
using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using boost::shared_ptr;
using namespace ::MathServer;
int main(int argc, char** argv) {
int port = 9090;
boost::shared_ptr<TTransport> socket(new TSocket("localhost", port));
boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
MathServiceClient client(protocol);
try {
transport->open();
cout << "1 + 1 = " << client.add(1, 1) << endl;
cout << "1 * 2 = " << client.mul(1, 2) << endl;
cout << "8 / 2 = " << client.div(8, 2) << endl;
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}

编译

用 g++ 编译,先针对各个 .cpp 文件生成 .o 目标,然后链接 thrift 库,生成可执行文件 client 和 server。由于 thrift 里用到 c++11 里的 bind 方法,需要指定 -std=c++11。假设 thrift 库安装在 /usr/local/lib 里,thrift 的头文件在 /usr/local/include/thrift 中,则完整的编译命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# build object files
g++ -I/usr/local/include -std=c++11 -c -o \
gen-cpp/calculator_constants.o gen-cpp/calculator_constants.cpp
g++ -I/usr/local/include -std=c++11 -c -o \
gen-cpp/calculator_types.o gen-cpp/calculator_types.cpp
g++ -I/usr/local/include -std=c++11 -c -o \
gen-cpp/MathService.o gen-cpp/MathService.cpp
# build server.o
g++ -I/usr/local/include -std=c++11 -c -o \
server.o server.cpp
# build client.o
g++ -I/usr/local/include -std=c++11 -c -o \
client.o client.cpp
# build target files: client, server
g++ -L/usr/local/lib -lthrift -o server server.o \
gen-cpp/calculator_constants.o gen-cpp/calculator_types.o \
gen-cpp/MathService.o
g++ -L/usr/local/lib -lthrift -o client client.o \
gen-cpp/calculator_constants.o gen-cpp/calculator_types.o \
gen-cpp/MathService.o
# clear *.o
rm ./*.o gen-cpp/*.o

可以直接执行以上命令,也可以将以上命令写进一个 build.sh 文件里,然后执行:

1
sh ./build.sh

生成完 client 和 server 后,分别在两个终端执行 ./server./client,可以分别看到 Start server...1 + 1 = 2 ...

综上。

Thrift 脚本语法

Thrift 用于跨语言的 RPC 通信,因此其接口定义文件 *.thrift 中包含的数据结构与函数定义必须对于不同语言都适用。官网的 Thrift Wiki Tutorial 基本包含了所有的 Thrift 语法,无须赘述,这里只是把它复制过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#
# Thrift Tutorial
# Mark Slee (mcslee@facebook.com)
#
# This file aims to teach you how to use Thrift, in a .thrift file. Neato. The
# first thing to notice is that .thrift files support standard shell comments.
# This lets you make your thrift file executable and include your Thrift build
# step on the top line. And you can place comments like this anywhere you like.
#
# Before running this file, you will need to have installed the thrift compiler
# into /usr/local/bin.
/**
* The first thing to know about are types. The available types in Thrift are:
*
* bool Boolean, one byte
* byte Signed byte
* i16 Signed 16-bit integer
* i32 Signed 32-bit integer
* i64 Signed 64-bit integer
* double 64-bit floating point value
* string String
* map<t1,t2> Map from one type to another
* list<t1> Ordered list of one type
* set<t1> Set of unique elements of one type
*
* Did you also notice that Thrift supports C style comments?
*/
// Just in case you were wondering... yes. We support simple C comments too.
/**
* Thrift files can reference other Thrift files to include common struct
* and service definitions. These are found using the current path, or by
* searching relative to any paths specified with the -I compiler flag.
*
* Included objects are accessed using the name of the .thrift file as a
* prefix. i.e. shared.SharedObject
*/
include "shared.thrift"
/**
* Thrift files can namespace, package, or prefix their output in various
* target languages.
*/
namespace cpp tutorial
namespace java tutorial
php_namespace tutorial
namespace perl tutorial
namespace smalltalk.category Thrift.Tutorial
/**
* Thrift lets you do typedefs to get pretty names for your types. Standard
* C style here.
*/
typedef i32 MyInteger
/**
* Thrift also lets you define constants for use across languages. Complex
* types and structs are specified using JSON notation.
*/
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
/**
* You can define enums, which are just 32 bit integers. Values are optional
* and start at 1 if not supplied, C style again.
* ^ ThriftIDL page says "If no constant value is supplied,
* the value is either 0 for the first element, or one greater than the
* preceding value for any subsequent element" so I'm guessing that's a bug.
* PS: http://enel.ucalgary.ca/People/Norman/enel315_winter1997/enum_types/ states that if values are not supplied, they start at 0 and not 1.
*/
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
/**
* Structs are the basic complex data structures. They are comprised of fields
* which each have an integer identifier, a type, a symbolic name, and an
* optional default value.
*
* Fields can be declared "optional", which ensures they will not be included
* in the serialized output if they aren't set. Note that this requires some
* manual management in some languages.
*/
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
/**
* Structs can also be exceptions, if they are nasty.
*/
exception InvalidOperation {
1: i32 what,
2: string why
}
/**
* Ahh, now onto the cool part, defining a service. Services just need a name
* and can optionally inherit from another service using the extends keyword.
*/
service Calculator extends shared.SharedService {
/**
* A method definition looks like C code. It has a return type, arguments,
* and optionally a list of exceptions that it may throw. Note that argument
* lists and exception lists are specified using the exact same syntax as
* field lists in struct or exception definitions. NOTE: Overloading of
* methods is not supported; each method requires a unique name.
*/
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* This method has an oneway modifier. That means the client only makes
* a request and does not listen for any response at all. Oneway methods
* must be void.
*
* The server may execute async invocations of the same client in parallel/
* out of order.
*/
oneway void zip(),
}
/**
* It's possible to declare more than one service per Thrift file.
*/
service CalculatorExtreme extends shared.SharedService {
void pingExtreme(),
}
/**
* That just about covers the basics. Take a look in the test/ folder for more
* detailed examples. After you run this file, your generated code shows up
* in folders with names gen-<language>. The generated code isn't too scary
* to look at. It even has pretty indentation.
*/

参考链接

[1] Apache Thrift OS X Setup
[2] mac os x10.10 安装thrift
[3] Thrift C++ Tutorial