本文主要是对Thrift学习的一些总结,主要讲述了Thrift的开发、基本数据类型服务类型的介绍,然后会通过两个例子来学习如何使用Thrift进行开发,同时会介绍Thrift在大数据框架方面的应用。

thrift 介绍

Apache Thrift 是由 Facebook 开发的一种远程服务调用(RPC Remote Procedure Call)的框架。下面应用官网的一句话对其进行介绍:

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

简而言之,Thrift是一种支持多语言的软件框架,在各个服务之间的RPC通信领域应用非常广泛。RPC(远程过程调用)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。(参考远程过程调用)。

Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。

Thrift安装

安装可以参考Apache Thrift Install,我使用的是CentOS7.1,直接使用yum安装即可。

1
2
3
4
5
6
7
#安装
$ sudo yum install thrift
#查看版本
$ thrift -version
# Output: Thrift version 0.9.1

Thrift Type

使用Thrift时,涉及到了.thrift文件(也就是服务)的编写,因此,需要了解一下Thrift Types,它包含了基本类型,自定义的结构体,容器,异常等。关于这部分,Thrift 入门这篇文章,总结很不错,我这里也就直接使用了拿来主义,首稍微有些修改。

基本类型(Base Types)

  • bool: 布尔变量(A boolean value, one byte);
  • byte: 8位有符号整数(A signed byte);
  • i16: 16位有符号整数(A 16-bit signed integer);
  • i32: 32位有符号整数(A 32-bit signed integer);
  • i64: 64位有符号整数(A 64-bit signed integer);
  • double: 64位浮点数(A 64-bit floating point number);
  • binary: byte数组(A byte array);
  • string: 字符串(Encoding agnostic text or binary string);

Note:Thrift 不支持无符号整数,因为有些语言也不支持无符号整数,比如Java

容器类型(Containers)

  • list: 一系列由T类型的数据组成的有序列表,元素可以重复;
  • set: 一系列由T类型的数据组成的无序集合,元素不可重复
  • map: 一个字典结构,key为T1类型,value为T2类型;

Note:这些集合中的元素可以是除了服务的任何Thrift类型(包括结构体和异常)。

结构体(Struct)

结构体中包含一系列的强类型域,等同于无继承的class。可以看出struct写法很类似C语言的结构体。

1
2
3
4
5
6
struct Example {
1:i32 number=10,
2:i64 bigNumber,
3:list<double> decimals,
4:string name="thrifty"
}

可选与必选

Thrift提供两个关键字requiredoptional,分别用于表示对应的字段时必填的还是可选的。例如:

1
2
3
4
struct People {
1: required string name;
2: optional i32 age;
}

表示name是必填的,age是可选的。

联合(Union)

在一个结构体中,如果field之间的关系是互斥的,即只能有一个field被使用被赋值。在这种情况下,我们可以使用union来声明这个结构体,而不是一堆堆optional的field,语意上也更明确了。例如:

1
2
3
4
5
6
7
8
union JavaObjectArg {
1: i32 int_arg;
2: i64 long_arg;
3: string string_arg;
4: bool bool_arg;
5: binary binary_arg;
6: double double_arg;
}

异常(Exceptions)

可以自定义异常类型,所定义的异常会继承对应语言的异常基类,例如java,就会继承 java.lang.Exception.

1
2
3
4
exception InvalidOperation {
1: i32 what,
2: string why
}

服务(service)

Thrift定义服务相当于Java中创建Interface一样,创建的service经过代码生成命令之后就会生成客户端和服务端的框架代码。定义形式如下:

1
2
3
4
5
6
7
service Hello{
string helloString(1:string para)
i32 helloInt(1:i32 para)
bool helloBoolean(1:bool para)
void helloVoid()
string helloNull()
}

命名空间(namespace)

Thrift的命名空间相当于Java中的package的意思,主要目的是组织代码。thrift使用关键字namespace定义命名空间,如:

1
namespace java service.demo

注意末尾不能有分号,由此生成的代码,其包路径结构为service.demo.

Thrift支持的数据传输格式、数据传输方式和服务模型

协议(传输格式)

  • TBinaryProtocol: 二进制格式;
  • TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输;
  • TJSONProtocol:JSON格式;
  • TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
  • TDebugProtocol:使用易懂的可读的文本格式,以便于debug。

传输层(数据传输方式)

  • TSocket:阻塞式socker;
  • TFramedTransport:使用非阻塞方式,以frame为单位进行传输。
  • TFileTransport:以文件形式进行传输。
  • TMemoryTransport:将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream。
  • TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现。
  • TNonblockingTransport —— 使用非阻塞方式,用于构建异步客户端

服务模型

  • TSimpleServer:单线程服务器端使用标准的阻塞式 I/O,简单的单线程服务模型,常用于测试;
  • TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
  • TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)。

Thrift 简单使用

这里我们使用两个Thrift的示例来讲述Thrift的使用,第一例子比较简单,类似于Hello World这种。第二例子,我们会使用阻塞单线程多线程、非阻塞、半同步半阻塞等方式。

这里Maven工程的pom.xml文件的配置为:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>

Thrift之Hello World

本例是一个简单Thrift示例,具体代码参见thrifthello

1.接口定义

我们来编写一个thrift文件,定义服务端的接口定义个服务接口(hello.thrift).

1
2
3
4
5
6
7
8
namespace java hello.thrift
service Hello{
string helloString(1:string para)
i32 helloInt(1:i32 para)
bool helloBoolean(1:bool para)
void helloVoid()
string helloNull()
}

这里我们定义了5个不同的接口,接着使用Thrift对文件进行编译,产生对应的程序文件,以Java为例。

1
thrift —gen java hello.thrift

命令执行完成后,就会在gen-java/hello/thrift生成一个Hello.java的文件,将这个java文件放到hello/thrift中。

2.接口实现

前面只是定义了接口的签名,现在我们来对接口进行实现,实现类需要实现Hello.Iface接口,代码如下:

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
package matt.thrifthello;
import org.apache.thrift.TException;
public class HelloServiceImpl implements Hello.Iface {
public boolean helloBoolean(boolean para) throws TException {
System.out.printf("hello true/false");
return para;
}
public int helloInt(int para) throws TException {
System.out.println("hello times: " + para);
return para;
}
public String helloNull() throws TException {
System.out.println("hello null");
return null;
}
public String helloString(String para) throws TException {
System.out.println("hello " + para);
return para;
}
public void helloVoid() throws TException {
System.out.println("Hello World");
}
}

3.服务端代码的实现

下面来编写服务器端的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package matt.thrifthello;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class HelloServiceServer {
public static void main(String[] args) {
try {
// 设置服务端口为 9527
TServerSocket serverTransport = new TServerSocket(9527);
// 关联处理器与 Hello 服务的实现
TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Start server on port 9527...");
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}

4.客户端的实现

下面来编写客户端的代码:

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
package matt.thrifthello;
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;
import org.apache.thrift.transport.TTransportException;
public class HelloServiceClient {
public static void main(String[] args) {
try {
// 设置调用的服务地址为本地,端口为 9527
TTransport transport = new TSocket("localhost", 9527);
transport.open();
// 设置传输协议为 TBinaryProtocol
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
// 调用服务的 helloVoid 方法,向server发送数据
client.helloVoid();
transport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}

运行

代码完成后,可以先运行HelloServiceServer(会一直运行,需要手动关闭),然后再运行HelloServiceClient(调用一次helloVoid()方法之后,就会退出,不过可以重复运行,这样就可以在server端看到多次输出了),就可以在HelloServiceServer的输出结果中看到客户端打印的Hello World字符串。

Thrift之Account

本例是关于用户帐号的示例,会涉及到用户帐号的添加登陆注册以及查询等,详细的代码参考Thrift_Account.

1.接口定义

这里我们编写一个thrift文件,定义服务接口,先定义一个操作状态有两种选项登陆或者注册;然后定一个结构体User,它有四个属性;再声明一个自定义的异常类,最后定义服务接口的一些方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace java matt.thrift.account
enum Operation{
LOGIN=1,
REGISTER=2
}
struct User{
1: required i32 userId,
2: required string username,
3: required string password,
4: Operation op
}
exception InvalidOperation{
1: i32 code,
2: string reason
}
service Account{
void addUser(1:User user) throws (1: InvalidOperation e)
User queryUser(1:i32 id)
list<User> queryUserList()
}

接着对Thrift对文件进行编译,产生对应的程序文件,以Java为例。

1
thrift —gen java Account.thrift

命令执行完成后,就会在gen-java/matt/thrift/account生成多个java的文件,将这些java文件放到matt/thrift/account中。

2.接口实现

这里定义AccountService类来实现Account.Iface接口,主要实现了三个方法。

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
package matt.thrift.account;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.thrift.TException;
public class AccountService implements Account.Iface {
private static Map<String, String> namePw = new HashMap<>();
private static Map<String, Integer> nameId = new HashMap<>();
private static Map<String, Operation> nameOp = new HashMap<>();
private static Map<Integer, String> idname = new HashMap<>();
@Override
public void addUser(User user) throws TException {
int id = user.getUserId();
String name = user.getUsername();
String pass = user.getPassword();
Operation op = user.getOp();
if (name == null || name.length() == 0) {
throw new InvalidOperation(100, "The name should not be empty!");
}
if (namePw.containsKey(name)) {
throw new InvalidOperation(101, "The name has been used, please change the name!");
}
if (nameId.containsValue(id) || id <= 0) {
throw new InvalidOperation(102, "The id has been used or the id is invalid, please change the id!");
}
if (op == Operation.LOGIN) {
String password = namePw.get(name);
if (password != null && password.equals(pass)) {
System.out.println("Login success!! Hello " + name);
} else {
System.out.println("Login failed!! please check your username and password");
}
} else if (op == Operation.REGISTER) {
if (namePw.containsKey(name)) {
System.out.println("The username " + name + " has been registered, please change one.");
} else {
namePw.put(name, pass);
nameId.put(name, id);
nameOp.put(name, op);
idname.put(id, name);
System.out.println("Register success!! Hello " + name);
}
} else {
throw new InvalidOperation(103, "unknown operation: " + op.getValue());
}
}
@Override
public User queryUser(int id) throws TException {
System.out.println(id);
if (idname.containsKey(id)) {
User user = new User();
user.userId = id;
String name = idname.get(id);
user.username = name;
user.password = namePw.get(name);
user.op = nameOp.get(name);
return user;
} else {
System.out.println("The id: " + id + " does not exist!");
User user = new User();
user.userId = 0;
user.username = "";
user.password = "123456";
user.op = Operation.LOGIN;
return user;
}
}
@Override
public List<User> queryUserList() throws TException {
List<User> list = new ArrayList<User>();
for (String name : namePw.keySet()) {
User user = new User();
user.userId = nameId.get(name);
user.username = name;
user.password = namePw.get(name);
user.op = nameOp.get(name);
list.add(user);
}
return list;
}
}

3.服务端代码的实现

这里是服务器端的实现,主要使用多种方式来实现,这几种方式实现的差别是使用API的不同。

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
148
149
package matt.thrift.account;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class AccountServer {
public static int port = 8090;
/**
* 简单服务器类型 阻塞单线程
*
* @author matt
* @since Apr 7, 2016
* @throws
* void
*/
public static void startSimpleServer() {
TProcessor tprocessor = new Account.Processor(new AccountService());
try {
// 创建trsnaport 阻塞通信
TServerSocket serverTransport = new TServerSocket(port);
// 创建protocol
TBinaryProtocol.Factory protocol = new TBinaryProtocol.Factory();
// 将processor transport protocol设入到服务器server中
TServer.Args args = new TServer.Args(serverTransport);
args.processor(tprocessor);
args.protocolFactory(protocol);
// 定义服务器类型 设定参数
TServer server = new TSimpleServer(args);
// 开启服务
System.out.println("Starting the Account server...");
server.serve();
} catch (TTransportException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 多线程服务器 阻塞多线程
*
* @author matt
* @since Apr 7, 2016
* @throws
* void
*/
public static void startThreadPoolServer() {
// 创建processor
TProcessor tprocessor = new Account.Processor(new AccountService());
try {
// 创建transport 阻塞通信
TServerSocket serverTransport = new TServerSocket(port);
// 创建protocol 数据传输协议
TBinaryProtocol.Factory protocol = new TBinaryProtocol.Factory();
TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverTransport);
args.processor(tprocessor);
args.protocolFactory(protocol);
// 创建服务器类型 多线程
TServer server = new TThreadPoolServer(args);
// 开启服务
System.out.println("Starting the Account server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 非阻塞I/O
*
* @author matt
* @since Apr 7, 2016
* @throws
* void
*/
public static void startTNonblockingServer() {
// 创建processor
TProcessor tprocessor = new Account.Processor(new AccountService());
try {
// 创建transport 非阻塞 nonblocking
TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(port);
// 创建protocol 数据传输协议
TCompactProtocol.Factory protocol = new TCompactProtocol.Factory();
// 创建transport 数据传输方式 非阻塞需要用这种方式传输
TFramedTransport.Factory transport = new TFramedTransport.Factory();
TNonblockingServer.Args args = new TNonblockingServer.Args(serverTransport);
args.processor(tprocessor);
args.transportFactory(transport);
args.protocolFactory(protocol);
// 创建服务器 类型是非阻塞
TServer server = new TNonblockingServer(args);
// 开启服务
System.out.println("Starting the Account server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 半同步半异步的非阻塞I/O
*/
public static void startTHsHaServer() {
// 创建processor
TProcessor tprocessor = new Account.Processor(new AccountService());
try {
// 创建transport 非阻塞
TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(port);
// 非阻塞需要的传输方式
TFramedTransport.Factory transport = new TFramedTransport.Factory();
// 数据传输协议
TCompactProtocol.Factory protocol = new TCompactProtocol.Factory();
// 创建半同步半异步服务
THsHaServer.Args args = new THsHaServer.Args(serverTransport);
args.processor(tprocessor);
args.transportFactory(transport);
args.protocolFactory(protocol);
// 创建 服务类型
TServer server = new THsHaServer(args);
// 开启服务
System.out.println("Starting the Account server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
// 开启简单服务器
// AccountServer.startSimpleServer();
// 开启多线程服务器
// AccountServer.startThreadPoolServer();
// 非阻塞I/O
AccountServer.startTNonblockingServer();
// 半同步半异步的非阻塞I/O
// AccountServer.startTHsHaServer();
}
}

4.客户端的实现

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
148
149
150
151
152
153
package matt.thrift.account;
import java.util.List;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
public class AccountClient {
public static String ip = "localhost";
public static int port = 8090;
public static int time_out = 30000;
/**
* 使用阻塞式socket
*
* @author matt
* @since Apr 7, 2016
* @throws
* void
*/
public static void startSimpleClient() {
TTransport transport = null;
try {
// 创建Transport
transport = new TSocket(ip, port, time_out);
// 创建TProtocol
TProtocol protocol = new TBinaryProtocol(transport);
// 基于TTransport和TProtocol创建Client
Account.Client client = new Account.Client(protocol);
transport.open();
// 正常添加用户
User user1 = new User(001, "matt1", "123456", Operation.REGISTER);
client.addUser(user1);
User user2 = new User(002, "matt2", "123456", Operation.REGISTER);
client.addUser(user2);
User user3 = new User(003, "matt3", "123456", Operation.REGISTER);
client.addUser(user3);
User user4 = new User(004, "matt4", "123456", Operation.REGISTER);
client.addUser(user4);
User user5 = new User(005, "matt5", "123456", Operation.REGISTER);
client.addUser(user5);
// 查看全部用户
List<User> list = client.queryUserList();
System.out.println("There are " + list.size() + " users in total.");
for (User user : list) {
System.out.println(user.userId + " " + user.username + " " + user.password);
}
// 查询用户
User userq1 = client.queryUser(1);
if (userq1 != null) {
System.out.println("Query: " + userq1.userId + " " + userq1.username + " " + userq1.password);
} else {
System.out.println("The id: 1 does not exist!");
}
User userq2 = client.queryUser(8);
if (userq2 != null) {
System.out.println("Query: " + userq2.userId + " " + userq2.username + " " + userq2.password);
} else {
System.out.println("The id: 8 does not exist!");
}
// 登陆用户
User users = new User(005, "matt5", "123456", Operation.LOGIN);
client.addUser(users);
// 添加异常用户
User user6 = new User(006, "", "123456", Operation.REGISTER);// name=null
client.addUser(user6);
User user7 = new User(006, "matt1", "123456", Operation.REGISTER);// name存在
client.addUser(user7);
User user8 = new User(005, "matt6", "123456", Operation.REGISTER);// id异常
client.addUser(user8);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 非阻塞
*
* @author matt
* @since Apr 7, 2016
* @throws
* void
*/
public static void startNonblockingClient() {
TTransport transport = null;
try {
transport = new TFramedTransport(new TSocket(ip, port));
TCompactProtocol protocol = new TCompactProtocol(transport);
Account.Client client = new Account.Client(protocol);
transport.open();
// 正常添加用户
User user1 = new User(001, "matt1", "123456", Operation.REGISTER);
client.addUser(user1);
User user2 = new User(002, "matt2", "123456", Operation.REGISTER);
client.addUser(user2);
User user3 = new User(003, "matt3", "123456", Operation.REGISTER);
client.addUser(user3);
User user4 = new User(004, "matt4", "123456", Operation.REGISTER);
client.addUser(user4);
User user5 = new User(005, "matt5", "123456", Operation.REGISTER);
client.addUser(user5);
// 查看全部用户
List<User> list = client.queryUserList();
System.out.println("There are " + list.size() + " users in total.");
for (User user : list) {
System.out.println(user.userId + " " + user.username + " " + user.password);
}
// 查询用户
User userq1 = client.queryUser(1);
if (!userq1.username.equals("")) {
System.out.println("Query: " + userq1.userId + " " + userq1.username + " " + userq1.password);
} else {
System.out.println("The id: 1 does not exist!");
}
User userq2 = client.queryUser(8);
if (!userq2.username.equals("")) {
System.out.println("Query: " + userq2.userId + " " + userq2.username + " " + userq2.password);
} else {
System.out.println("The id: 8 does not exist!");
}
// 登陆用户
User users = new User(005, "matt5", "123456", Operation.LOGIN);
client.addUser(users);
// 添加异常用户
User user6 = new User(006, "", "123456", Operation.REGISTER);// name=null
client.addUser(user6);
User user7 = new User(006, "matt1", "123456", Operation.REGISTER);// name存在
client.addUser(user7);
User user8 = new User(005, "matt6", "123456", Operation.REGISTER);// id异常
client.addUser(user8);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// AccountClient.startSimpleClient();
AccountClient.startNonblockingClient();
}
}

运行

代码完成后,可以先运行AccountServer(会一直运行,需要手动关闭),然后再运行AccountClient

上面程序服务器端的运行的结果为:

1
2
3
4
5
6
7
8
9
Starting the Account server...
Register success!! Hello matt1
Register success!! Hello matt2
Register success!! Hello matt3
Register success!! Hello matt4
Register success!! Hello matt5
1
8
The id: 8 does not exist!

客户端运行的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
There are 5 users in total.
1 matt1 123456
3 matt3 123456
2 matt2 123456
5 matt5 123456
4 matt4 123456
Query: 1 matt1 123456
The id: 8 does not exist!
InvalidOperation(code:101, reason:The name has been used, please change the name!)
at matt.thrift.account.Account$addUser_result$addUser_resultStandardScheme.read(Account.java:1165)
at matt.thrift.account.Account$addUser_result$addUser_resultStandardScheme.read(Account.java:1)
at matt.thrift.account.Account$addUser_result.read(Account.java:1101)
at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:86)
at matt.thrift.account.Account$Client.recv_addUser(Account.java:93)
at matt.thrift.account.Account$Client.addUser(Account.java:80)
at matt.thrift.account.AccountClient.startNonblockingClient(AccountClient.java:135)
at matt.thrift.account.AccountClient.main(AccountClient.java:151)

Thrift结构

Thrift包含了一个完整的堆栈结构用于构建客户端和服务器端,下面描述了Thrift的整体的架构。

selection_sort

  • 黄色部分:是用户实现的业务逻辑;
  • 褐色部分:是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架;
  • 红色部分:是根据 Thrift 文件生成代码实现数据的读写操作;
  • TProtocol:是协议层, 定义数据传输格式,可以为二进制或者XML等;
  • TTransport:是传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等。

参考: