<a href="2018/04/20/Thrift%20 入门"> 上一篇文章 </a > 我们了解了 thrift 的概念以及类型系统,本文我们通过一个简单的实例来更深入地了解 thrift 的使用。我们的实例非常简单,就是实现一个登录注册功能,其用户名密码缓存在内存中。

# 编写 thrift 文件

我们编写一个 account.thrift 的文件。

namespace java me.wuchong.thrift.generated
enum Operation{
  LOGIN = 1,
  REGISTER = 2
}
struct Request{
  1: string name,
  2: string password,
  3: Operation op
}
exception InvalidOperation{
  1: i32 code,
  2: string reason
}
service Account{
  string doAction(1: Request request) throws (1: InvalidOperation e);
}

然后在命令行下运行如下命令:

thrift --gen java account.thrift

则会在当前目录生成 gen-java 目录,该目录下会按照 namespace 定义的路径名一次一层层生成文件夹,如下图所示,在指定的包路径下生成了 4 个类。

# 服务实现

到此为止,thrift 已经完成了其工作。接下来我们需要做的就是实现 Account 接口里的具体逻辑。我们创建一个 AccountService 类,实现 Account.Iface 接口。逻辑非常简单,将用户账户信息缓存在内存中,实现登录注册的功能,并且对一些非法输入状况抛出异常。

package me.wuchong.thrift.impl;
import me.wuchong.thrift.generated.Account;
import me.wuchong.thrift.generated.InvalidOperation;
import me.wuchong.thrift.generated.Operation;
import me.wuchong.thrift.generated.Request;
import java.util.HashMap;
import java.util.Map;
/**
 * Created by wuchong on 15/10/7.
 */
public class AccountService implements Account.Iface {
    private static Map<String, String> accounts = new HashMap<>();
    @Override
    public String doAction(Request request) throws InvalidOperation {
        String name = request.getName();
        String pass = request.getPassword();
        Operation op = request.getOp();
        System.out.println(String.format("Get request[name:%s, pass:%s, op:%d]", name, pass, op.getValue()));
        if (name == null || name.length() == 0){
            throw new InvalidOperation(100, "param name should not be empty");
        }
        if (op == Operation.LOGIN) {
            String password = accounts.get(name);
            if (password != null && password.equals(pass)) {
                return "Login success!! Hello " + name;
            } else {
                return "Login failed!! please check your username and password";
            }
        } else if (op == Operation.REGISTER) {
            if (accounts.containsKey(name)) {
                return String.format("The username '%s' has been registered, please change one.", name);
            } else {
                accounts.put(name, pass);
                return "Register success!! Hello " + name;
            }
        } else {
            throw new InvalidOperation(101, "unknown operation: " + op.getValue());
        }
    }
}

# 启动服务端和客户端

我们实现了服务的具体逻辑,接下来需要启动该服务。这里我们需要用到 thrift 的依赖包。在 pom.xml 中加入对 thrift 的依赖。

<dependency>
  <groupId>org.apache.thrift</groupId>
  <artifactId>libthrift</artifactId>
  <version>0.9.2</version>
</dependency>

注:如果你的依赖中没有加入 slf4j 的实现,则需要加上 slf4j-log4j12 或者 logback 的依赖,因为 thrift 有用到 slf4j

启动服务的实现如下:

package me.wuchong.thrift.impl;
import me.wuchong.thrift.generated.Account;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
/**
 * Created by wuchong on 15/10/7.
 */
public class AccountServer {
    public static void main(String[] args) throws Exception {
        TServerSocket socket = new TServerSocket(9999);
        Account.Processor processor = new Account.Processor<>(new AccountService());
        TServer server = new TSimpleServer(new TServer.Args(socket).processor(processor));
        System.out.println("Starting the Account server...");
        server.serve();
    }
}

运行之后,可以在控制台看到输出:

Starting the Account server...

目前服务已经启动,则在客户端就可以进行 RPC 调用了。启动客户端的代码如下:

package me.wuchong.thrift.impl;
import me.wuchong.thrift.generated.Account;
import me.wuchong.thrift.generated.InvalidOperation;
import me.wuchong.thrift.generated.Operation;
import me.wuchong.thrift.generated.Request;
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;
/**
 * Created by wuchong on 15/10/7.
 */
public class AccountClient {
    public static void main(String[] args) throws TException {
        TTransport transport = new TSocket("localhost", 9999);
        transport.open();   //建立连接
        TProtocol protocol = new TBinaryProtocol(transport);
        Account.Client client = new Account.Client(protocol);
        //第一个请求, 登录 wuchong 帐号
        Request req = new Request("wuchong", "1234", Operation.LOGIN);
        request(client, req);
        //第二个请求, 注册 wuchong 帐号
        req.setOp(Operation.REGISTER);
        request(client, req);
        //第三个请求, 登录 wuchong 帐号
        req.setOp(Operation.LOGIN);
        request(client, req);
        //第四个请求, name 为空的请求
        req.setName("");
        request(client, req);
        transport.close();  //关闭连接
    }
    public static void request(Account.Client client, Request req) throws TException{
        try {
            String result = client.doAction(req);
            System.out.println(result);
        } catch (InvalidOperation e) {
            System.out.println(e.reason);
        }
    }
}

运行客户端,其结果如下所示。

Login failed!! please check your username and password
Register success!! Hello wuchong
Login success!! Hello wuchong
param name should not be empty

而此时,服务端会打印出收到的请求信息。

Starting the Account server...
Get request[name:wuchong, pass:1234, op:1]
Get request[name:wuchong, pass:1234, op:2]
Get request[name:wuchong, pass:1234, op:1]
Get request[name:, pass:1234, op:1]

你可以发现,只需要几行代码,我们就实现了高效的 RPC 通信。

# 参考资料

  • Thrift: The Missing Guide
  • Thrift Tutorial
  • thrift 入门教程