Thrift入门学习

Thrift

一、简介

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

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

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

二、Thrift安装

1、Mac OS

# 安装
# 没有brew需要先安装Homebrew
brew install thrift

# 查看thrift版本
thrift -version
# output:Thrift version 0.14.2

2、Ubuntu


三、Thrift数据类型

使用Thrift时,涉及到了.thrift文件(也就是服务)的编写,因此,需要了解一下Thrift Types,它包含了基本类型,自定义的结构体,容器,异常等

1、基本类型

  • 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);
  • string: 字符串(Encoding agnostic text or binary string)
  • binary:未编码的字节序列,byte数组。

2、容器类型

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

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

3、结构体(Struct)

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

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

4、可选和必选(Required,Optional)

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

struct People {
    1: required string name;   //name是必须的
    2: optional i32 age;			 //age是可选的
}

5、联合(Union)

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

typedef i32 int
typedef i64 long

union JavaObjectArg {
  1: int int_arg;
  2: long long_arg;
  3: string string_arg;
  4: bool bool_arg;
  5: binary binary_arg;
  6: double double_arg;
}

6、异常(Exception)

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

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

7、服务(Service)

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

service Hello{
  string helloString(1:string para)
  i32 helloInt(1:i32 para)
  bool helloBoolean(1:bool para)
  void helloVoid()
  string helloNull()
}

8、命名空间

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

namespace java service.demo

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

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

1、数据传输格式(协议)

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

2、数据传输方式(传输层)

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

3、服务模型

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

五、Thrift入门

首先需要添加Maven依赖:

<!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
<dependency>
  <groupId>org.apache.thrift</groupId>
  <artifactId>libthrift</artifactId>
  <version>0.14.2</version>
</dependency>

Thrift项目地址

1、Hello World 项目

thrift入门项目,简单介绍thrift的使用,只包含一个service。

示例给出客户端和服务器的通信和客户端调用服务端的服务。

(1)接口定义

编写一个.thrift文件,定义一个服务接口:
hello.thrift

// filename: hello.thrift
namespace java thrift.hello  // thrift.hello 表示后面生成的代码放到哪个位置
service Hello{
 string helloString(1:string para)
 i32 helloInt(1:i32 para)
 bool helloBoolean(1:bool para)
 void helloVoid()
 string helloNull()
}

这里定义了一个Hello接口,里面包括了5个方法。接着使用Thrift对文件进行编译,产生对应的程序文件,以Java为例:

thrift -gen java hello.thrift

命令执行完成后,就会在gen-java/thrift/hello/生成一个Hello.java文件,将这个Java文件放到thrift.hello包下。
在这里插入图片描述

将上述文件夹内的Java文件Hello.java移动到thrift.hello中:
在这里插入图片描述

(2)接口实现

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

//filename: HelloServiceImpl.java
package thrift.hello.impl;

import org.apache.thrift.TException;
import thrift.hello.Hello;

public class HelloServiceImpl implements Hello.Iface {
    @Override
    public String helloString(String para) throws TException {
        System.out.println("helloString: " + para);
        return para;
    }

    @Override
    public int helloInt(int para) throws TException {
        System.out.println("helloInt: " + para);
        return para;
    }

    @Override
    public boolean helloBoolean(boolean para) throws TException {
        System.out.println("helloBoolean: " + para);
        return para;
    }

    @Override
    public void helloVoid() throws TException {
        System.out.println("helloVoid");
    }

    @Override
    public String helloNull() throws TException {
        System.out.println("helloNull");
        return null;
    }
}

代码目录如下:
在这里插入图片描述

(3)服务端代码实现

// filaname: HelloServiceServer.java
package thrift.hello;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import thrift.hello.impl.HelloServiceImpl;

public class HelloServiceServer {
    public static void main(String[] args) {
        try{
          	// 创建阻塞式socket,绑定端口9527
            TServerSocket serverSocket = new TServerSocket(9527);
          	// 将接口实现绑定到process
            TProcessor processor = new Hello.Processor<HelloServiceImpl>(new HelloServiceImpl());
          	// 使用二进制协议传输  
          	TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
            TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverSocket).processor(processor).protocolFactory(factory));
            System.out.println("system server on port 9527...");
            server.serve();
        }catch (TTransportException e){
            e.printStackTrace();
        }
    }
}

(4)客户端实现代码

package thrift.hello;

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{
            TTransport transport = new TSocket("localhost",9527);
            transport.open();
            TProtocol protocol = new TBinaryProtocol(transport);
            Hello.Client client = new Hello.Client(protocol);
            
          	// 远程调用服务端的helloString方法
            String result = client.helloString("zsj");
            System.out.println(result);
            transport.close();
        }catch (TTransportException e){
            e.printStackTrace();
        }catch (TException e){
            e.printStackTrace();
        }
    }
}

(5)运行结果

首先运行服务端,启动后服务端会一直运行:

在这里插入图片描述

然后运行客户端,启动后回调用服务端的方法:

在这里插入图片描述

输出方法返回的数据。

项目整体文件结构

在这里插入图片描述

2、Account 项目

在Hello World项目上增加了几个不同的数据类型,包括枚举、异常、结构体。

示例给出客户端和服务器连接交互的方式,包括阻塞和非阻塞IO,同步异步等等,同时给出客户端调用服务器的服务。

(1)接口定义

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

// filename: account.thrift
namespace java 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()
}

接着对account.thrift进行编译,然后按照Hello World项目中的,将生成的文件移动到对应的文件夹thrfit.account中。

thrift -gen java account.thrift

在这里插入图片描述

(2)接口实现

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

// filename : AccountServiceImpl.java
package thrift.account.impl;


//import org.apache.commons.codec.binary.StringUtils;

import org.apache.thrift.TException;

import thrift.account.Account;
import thrift.account.InvalidOperation;
import thrift.account.Operation;
import thrift.account.User;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//import org.apache.commons.lang.StringUtils;

/**
 * @author zsj
 */
public class AccountServiceImpl 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 InvalidOperation, TException {

        // 校验参数
        if (null == user.getUsername() || user.getUsername().length() <= 0) {
            throw new InvalidOperation(100, "The name should not be empty");
        }
        if (namePw.containsKey(user.getUsername())) {
            throw new InvalidOperation(101, "The name has been used, please change the name!");
        }
        if (nameId.containsValue(user.getUserId()) || user.getUserId() <= 0) {
            throw new InvalidOperation(102, "The id has been used or the id is invalid, please change the id!");
        }

        switch (user.getOp()) {
            case LOGIN: {
                String password = user.getPassword();
                String currPassword = namePw.get(user.getUsername());
                if (null != currPassword && currPassword.equals(password)) {
                    System.out.println("login success! hello " + user.getUsername());
                } else {
                    System.out.println("login failed! please check your password and username");
                }

            }
            break;
            case REGISTER: {
                if (namePw.containsKey(user.getUsername())) {
                    System.out.println("The username has registered,please change one");
                } else {
                    namePw.put(user.getUsername(), user.getPassword());
                    nameId.put(user.getUsername(), user.getUserId());
                    nameOp.put(user.getUsername(), user.getOp());
                    idName.put(user.getUserId(), user.getUsername());
                    System.out.println("Register success! hello " + user.getUsername());
                }
            }
            break;
            default:
                throw new InvalidOperation(103, "unknown operation: " + user.getOp().getValue());
        }
    }

    @Override
    public User queryUser(int id) throws TException {
        if (idName.containsKey(id)) {
            return new User(id, idName.get(id).toString(),
                    namePw.get(idName.get(id)).toString(),
                    nameOp.get(idName.get(id)));
        } else {
            System.out.println("the id:" + id + " not exist!");
//            return new User(-1,"","",Operation.LOGIN);
            return new User();
        }

    }

    @Override
    public List<User> queryUserList() throws TException {
        List<User> users = new ArrayList<>();
        for(String name : nameId.keySet()){
            users.add(new User(nameId.get(name),name,namePw.get(name),nameOp.get(name)));
        }
        return users;
    }
}

目录位置:

在这里插入图片描述

(3)服务端代码实现

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

package thrift.account;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.*;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
import thrift.account.impl.AccountServiceImpl;

public class AccountServiceServer {
    public static final int port = 8090;


    public static void main(String[] args) {
        // 开启简单服务器类型
//        startSimpleServer();

//        startThreadPoolServer();

        startTNonblockingServer();

//        startTHsHaServer();
    }

    /**
     * @brief 简单服务器类型,阻塞单线程
     */
    public static void startSimpleServer(){
        TProcessor processor = new Account.Processor<Account.Iface>(new AccountServiceImpl());
        try{
            TServerSocket socket = new TServerSocket(port);
            TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
            TServer.Args args = new TServer.Args(socket);
            args.processor(processor);
            args.protocolFactory(factory);
            TServer server = new TSimpleServer(args);

            System.out.println("start simple account server...");
            server.serve();
        }catch (TTransportException e){
            e.printStackTrace();
        }

    }

    /**
     * @brief 多线程服务器,阻塞多线程
     */
    public static void startThreadPoolServer(){
        TProcessor processor = new Account.Processor<Account.Iface>(new AccountServiceImpl());
        try{
            TServerSocket socket = new TServerSocket(port);
            TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
            TThreadPoolServer.Args args = new TThreadPoolServer.Args(socket);
            args.processor(processor);
            args.protocolFactory(factory);
            TThreadPoolServer server = new TThreadPoolServer(args);

            System.out.println("start thread pool account server...");
            server.serve();
        }catch (TTransportException e){
            e.printStackTrace();
        }
    }

    /**
     * @brief 非阻塞IO
     */
    public static void startTNonblockingServer(){
        TProcessor processor = new Account.Processor<Account.Iface>(new AccountServiceImpl());
        try{
            TNonblockingServerSocket socket = new TNonblockingServerSocket(port);
            TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();
            TFramedTransport.Factory transportFactory = new TFramedTransport.Factory();
            TNonblockingServer.Args args = new TNonblockingServer.Args(socket);
            args.processor(processor);
            args.transportFactory(transportFactory);
            args.protocolFactory(protocolFactory);
            TNonblockingServer server = new TNonblockingServer(args);

            System.out.println("start nonblocking account server...");
            server.serve();
        }catch (TTransportException e){
            e.printStackTrace();
        }
    }

    /**
     * @brief 半同步半异步的非阻塞IO
     */
    public static void startTHsHaServer(){
        TProcessor processor = new Account.Processor<Account.Iface>(new AccountServiceImpl());
        try{
            TNonblockingServerTransport socket = new TNonblockingServerSocket(port);
            TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();
            TFramedTransport.Factory transportFactory = new TFramedTransport.Factory();
            THsHaServer.Args args = new THsHaServer.Args(socket);
            args.processor(processor);
            args.transportFactory(transportFactory);
            args.protocolFactory(protocolFactory);
            TServer server = new THsHaServer(args);

            System.out.println("start hsha account server...");
            server.serve();
        }catch (TTransportException e){
            e.printStackTrace();
        }
    }
}

(4)客户端代码实现

package thrift.account;

import org.apache.thrift.TConfiguration;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
import org.omg.PortableInterceptor.ACTIVE;

import java.util.List;

public class AccountServiceClient {
    private static final String IP = "localhost";
    private static final int PORT = 8090;
    private static final int TIME_OUT = 30000;


    public static void main(String[] args) {
        startNonblockingClient();
//        startSimpleClient();
    }


    static void startSimpleClient(){
        TTransport transport = null;
        try {
            transport = new TSocket(new TConfiguration(),IP, PORT,TIME_OUT);
            TProtocol protocol = new TBinaryProtocol(transport);
            Account.Client client = new Account.Client(protocol);
            transport.open();

            // 正常添加用户
            User user1 = new User(1, "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 (TException e) {
            e.printStackTrace();
        }
    }

    static void startNonblockingClient(){
        TTransport transport = null;
        try {
            transport = new TFramedTransport(new TSocket(IP,PORT));
            TProtocol protocol = new TCompactProtocol(transport);
            Account.Client client = new Account.Client(protocol);
            transport.open();

            // 正常添加用户
            User user1 = new User(1, "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 (TException e) {
            e.printStackTrace();
        }
    }
}

(5)运行结果

首先运行服务端,服务端启动可以选择不同方式启动:

在这里插入图片描述

在这里插入图片描述

然后运行客户端,也可以选择不同的方式:

在这里插入图片描述

在这里插入图片描述

项目整体文件结果

在这里插入图片描述

3、Student 项目

主要是为了表现thrift怎么在SpringBoot项目中进行工作,将thrift整合到SpringBoot中,并将服务器和客户端分离,后续将服务端部署到远程服务器,真正用客户端进行远端调用。

接口定义

服务端和客户端是两个分开的项目,但是需要共用同一套接口定义。

// filename: student.thrift
namespace java com.example.thriftdemo.thrift

typedef i16 short
typedef i32 int
typedef string String
typedef bool boolean

// 定义对象
struct Student{
    1:optional String name,
    2:optional int age,
    3:optional String address
}

// 定义异常
exception DataException{
    1:optional int code,
    2:optional String message,
    3:optional String dateTime
}

// 定义后台业务接口
service StudentService{
    Student getStudentByName(1:required String name) throws (1:DataException dataExeption),
    void save(1:required Student student) throws (1:DataException dataException)
}

(1)服务端

使用命令编译上述thrift文件后,将生成的文件移动到特定的目录下:

thrift -gen java student.thrift

在这里插入图片描述

a.接口实现
package com.example.thriftdemo.thrift.impl;

import com.example.thriftdemo.thrift.DataException;
import com.example.thriftdemo.thrift.Student;
import com.example.thriftdemo.thrift.StudentService;
import org.apache.thrift.TException;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;


@Service
public class StudentServiceImpl implements StudentService.Iface {

    private Map<String,Student> studentMap;

    @PostConstruct
    public void init(){
        studentMap = new HashMap<>(16);
        Student student1 = new Student();
        student1.setAddress("Shanghai");
        student1.setAge(16);
        student1.setName("test");
        studentMap.put("test",student1);

        Student student2 = new Student();
        student2.setAddress("Beijing");
        student2.setAge(19);
        student2.setName("dev");
        studentMap.put("dev",student2);
    }


    @Override
    public Student getStudentByName(String name) throws DataException, TException {
        System.out.println("remote call getStudentByName. param: name="+name);
        return studentMap.get(name);
    }

    @Override
    public void save(Student student) throws DataException, TException {
        System.out.println("remote call save. param: student="+student);
        studentMap.put(student.getName(),student);
    }
}
b.服务端代码实现
package com.example.thriftdemo.thrift.server;

import com.example.thriftdemo.thrift.Student;
import com.example.thriftdemo.thrift.StudentService;
import com.example.thriftdemo.thrift.impl.StudentServiceImpl;
import org.apache.thrift.TProcessor;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFramedTransport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ThriftServer {


    @Value("${thrift.port}")
    private Integer port;

    @Value("${thrift.min-thread-pool}")
    private Integer minThreadPool;

    @Value("${thrift.max-thread-pool}")
    private Integer maxThreadPool;

    @Autowired
    private StudentServiceImpl studentService;

    public void start() {
        try {
            TNonblockingServerTransport serverSocket = new TNonblockingServerSocket(port);
            THsHaServer.Args args = new THsHaServer.Args(serverSocket)
                    .minWorkerThreads(minThreadPool).maxWorkerThreads(maxThreadPool);
            TProcessor processor = new StudentService.Processor<StudentServiceImpl>(studentService);

            // 二进制协议
            args.protocolFactory(new TCompactProtocol.Factory());
            args.transportFactory(new TFramedTransport.Factory());
            args.processorFactory(new TProcessorFactory(processor));

            TServer server = new THsHaServer(args);
            System.out.println("Thrift Server Started at port: " + port);
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }

    }
}
c.主函数配置
package com.example.thriftdemo;

import com.example.thriftdemo.thrift.server.ThriftServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;

// 禁止数据库的自动配置
@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class})
public class ThriftDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThriftDemoApplication.class, args);
    }

		
  	// 初始化ThriftServer,开启thrift服务
    @Bean(initMethod = "start")
    public ThriftServer init(){
        return new ThriftServer();
    }
}
d. yml配置
thrift:
  port: 7002
  min-thread-pool: 100
  max-thread-pool: 200
e. 运行结果
f. 项目文件结构

在这里插入图片描述

(2)客户端

现将编译thrift文件产生的文件移动到特定的目录(服务端和客户端文件一致)。

在这里插入图片描述

a. 接口定义实现
  • 定义:

    package com.example.thriftdemoclient.service;
    
    import com.example.thriftdemoclient.thrift.Student;
    
    public interface StudentService {
        Student getStudentByName(String name);
        void save(Student student);
    }
    
  • 实现:

    package com.example.thriftdemoclient.service.impl;
    
    
    import com.example.thriftdemoclient.client.ThriftClient;
    import com.example.thriftdemoclient.service.StudentService;
    import com.example.thriftdemoclient.thrift.Student;
    import org.apache.thrift.TException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class StudentServiceImpl implements StudentService {
    
        @Autowired
        private ThriftClient client;
    
        @Override
        public Student getStudentByName(String name) {
            try {
                client.open();
                Student student = client.getService().getStudentByName(name);
                System.out.println("获取用户信息成功,用户信息为:" + student);
                return student;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                client.close();
            }
            return null;
        }
    
        @Override
        public void save(Student student) {
            try {
                client.open();
                client.getService().save(student);
                System.out.println("保存用户信息成功,用户信息为:" + student);
    
            } catch (TException e) {
                e.printStackTrace();
            } finally {
                client.close();
            }
        }
    }
    
  • 文件结构:

在这里插入图片描述

b. 客户端代码实现
package com.example.thriftdemoclient.client;

import com.example.thriftdemoclient.thrift.StudentService;
import lombok.Setter;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.layered.TFastFramedTransport;
import org.apache.thrift.transport.layered.TFramedTransport;
import org.springframework.stereotype.Component;


public class ThriftClient {
    @Setter
    private Integer port;
    @Setter
    private String host;
    private TTransport transport;
    private TProtocol protocol;
    private StudentService.Client client;

    private void initClient() throws TTransportException {
        transport = new TFramedTransport(new TSocket(host,port),1000);
        protocol = new TCompactProtocol(transport);
        client = new StudentService.Client(protocol);
    }

    public StudentService.Client getService(){
        return client;
    }

    public void open() throws TTransportException {
        if(null != transport && !transport.isOpen()){
            transport.open();
        }
    }

    public void close(){
        if(null != transport && transport.isOpen()){
            transport.close();
        }
    }

}
c. 添加配置
package com.example.thriftdemoclient.config;

import com.example.thriftdemoclient.client.ThriftClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ThriftClientConfig {

    @Value("${thrift.port}")
    private Integer port;

    @Value("${thrift.host}")
    private String host;

    @Bean(initMethod = "initClient")
    public ThriftClient init(){
        ThriftClient client = new ThriftClient();
        client.setHost(host);
        client.setPort(port);
        return client;
    }
}
d. yaml配置
thrift:
  port: 7002
  host: xl-zsj.top
server:
  port: 8001
e. 添加Controller

添加一个Controller用于测试

package com.example.thriftdemoclient.controller;


import com.example.thriftdemoclient.service.StudentService;
import com.example.thriftdemoclient.thrift.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rpc")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/getStudent")
    public Student getStudent(String name){
        System.out.println("request param: " + name);
        return studentService.getStudentByName(name);
    }

    @GetMapping("/saveStudent")
    public String saveStudent(String name,Integer age){
        Student student = new Student();
        student.setName(name);
        student.setAge(age);
        studentService.save(student);
        return "success";
    }
}
f. 项目文件结构

在这里插入图片描述

部署运行:

  • 将服务端代码打包上传到远程服务器,运行命令java -jar xxx运行程序

  • 在本地直接运行客户端代码

  • 通过Postman访问客户端提供的接口,客户端会远程调用服务端的服务

    • getStudent

在这里插入图片描述

可以看到返回数据成功,同样远程服务器也有日志打印:

在这里插入图片描述

  • save

在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值