Thrift简介

Thrift入门教程

Thrift最初由Facebook研发,主要用于各个服务之间的RPC通信,支持跨语言,常用的语言比如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml都支持。Thrift是一个典型的CS(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,没错,这种语言就是IDL(Interface Description Language)。

Thrift IDL

本节介绍Thrift的接口定义语言,Thrift IDL支持的数据类型包含:

基本类型

thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如java

bool:布尔值,true 或 false,对应 Java 的 boolean

byte: 有符号字节,对应Java的byte

i16: 16位有符号整数,对应Java的short

i32: 32位有符号整数,对应Java的int

i64: 64位有符号整数,对应Java的long

double: 64位浮点数,对应Java的double

string: 未知编码文本或二进制字符串,对应Java的String

容器类型

集合中的元素可以是除了service之外的任何类型,包括exception。

list: 一系列由T类型的数据组成的有序列表,元素可以重复

set: 一系列由T类型的数据组成的无序集合,元素不可重复

map:一个字典结构,key为K类型,value为V类型

结构体(struct)

就像C语言一样,thrift也支持struct类型,目的就是将一些数据聚合在一起,方便传输管理。在Java 中是一个JavaBean。成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用。struct的定义形式如下:

struct People {

     1: string name;

     2: i32 age;

     3: string sex;

}

字段会有optionalrequired之分,但是如果不指定则为无类型

1.无类型可以不填充值,但是在序列化传输的时候也会序列化进去,序列化之后值为对应数据类型的默认值:
(1)i32类型,在服务端使用isSetXxx()方法时,无论有没有填充结果返回true,服务端获取对应值为0 。
(2)string类型,如果没有填充值,在服务端使用isSetXxx()方法时结果返回false,在服务端获取对应值为null;如果填充null值,在服务端使用isSetXxx()方法时结果返回false,在服务端获取对应值为null。

2.optional是不填充则不序列化,如果没有填充值,在服务端使用isSetXxx()方法时结果返回false,获取该值时结果为对应数据类型的默认值:
(1)string类型如果填充结果为null,在服务端使用isSetXxx()方法时结果返回false,服务端获取结果为null;
(2)i32类型如果填充结果为0,在服务端使用isSetXxx()方法时结果返回true,服务端获取结果为0

3.required是必须填充也必须序列化,如果不填充该字段则会报错。

同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入。每个字段可以设置默认值。

枚举(enum)

枚举的定义形式和Java的Enum定义差不多,例如:

enum Sex {

    MALE,

    FEMALE

}

异常(exception)

thrift支持自定义exception,规则和struct一样,所定义的异常会继承对应语言的异常基类,例如java,就会继承 java.lang.Exception。如下:

exception RequestException {

    1: i32 code;

    2: string reason;

}

服务(service)

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

service HelloWordService {

// service中定义的函数,相当于Java interface中定义的函数

string doAction(1: string name, 2: i32 age);

}

类型定义

thrift支持类似C++一样的typedef定义,比如:

typedef i32 Integer

typedef i64 Long

注意,末尾没有逗号或者分号

常量(const)

thrift也支持常量定义,使用const关键字,例如:

const i32 MAX_RETRIES_TIME = 10

const string MY_WEBSITE = "http://qifuguang.me";

末尾的分号是可选的,可有可无,并且支持16进制赋值

命名空间

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

namespace java com.winwill.thrift

格式是:namespace 语言名 路径

注意末尾不能有分号。

文件包含

thrift也支持文件包含,相当于C/C++中的include,Java中的import。使用关键字include定义,例如:

include "global.thrift"

注释

thrift注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都单当做注释,/**/包裹的语句也是注释。


生成代码

知道了怎么定义thirtf文件之后,我们需要用定义好的thrift文件生成我们需要的目标语言的源码,本文以生成java源码为例。假设现在定义了如下一个thrift文件:Test.thrift

namespace java com.zl.thrift
enum RequestType {
   SAY_HELLO, //问好
   QUERY_TIME, //询问时间
}

struct Request {
   1: required RequestType type; // 请求的类型,必选
   2: string name; // 发起请求的人的名字
   3: string oldName; // 发起请求的人的名字
   4: i32 age; // 发起请求的人的年龄
   5: optional string address; //发起请求人的地址
   6: optional i32 account //发起请求人的邮编
}
exception RequestException {
   1: required i32 code;
   2: optional string reason;
}

// 服务名
service HelloWordService {
   string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
} 

在终端运行如下命令(将thrift-0.9.2.exe与.thrift文件放入同一个路径下,进入CMD执行命令):

thrift-0.9.2 --gen java Test.thrift

则在当前目录会生成一个gen-java目录,该目录下会按照namespace定义的路径名一次一层层生成文件夹,到gen-java\com\zl\thrif\目录下可以看到生成的4个Java类

可以看到,thrift文件中定义的enum,struct,exception,service都相应地生成了一个Java类,这就是能支持Java语言的基本的框架代码。

服务端实现

代码生成这一步已经将接口代码生成了,现在需要做的是实现HelloWordService的具体逻辑,实现的方式就是创建一个Java类,implements com.winwill.thrift.HelloWordService.Iface,例如:

package com.zl.thrift;

import org.apache.commons.lang.StringUtils;
import org.apache.thrift.TException;

import java.util.Date;

public class HelloWordServiceImpl implements com.zl.thrift.HelloWordService.Iface {
    // 实现这个方法完成具体的逻辑。
    public String doAction(com.zl.thrift.Request request) throws com.zl.thrift.RequestException, TException {
        System.out.println("Get request: " + request);
        if (StringUtils.isBlank(request.getName()) || request.getType() == null) {
            throw new com.zl.thrift.RequestException();
        }
        StringBuilder result = new StringBuilder();
        if (request.getType() == com.zl.thrift.RequestType.SAY_HELLO) {
            result.append("isSetName:").append(request.isSetName()).append("\n");
            result.append("isSetOldName:").append(request.isSetOldName()).append("\n");
            result.append("isSetAge:").append(request.isSetAge()).append("\n");
            result.append("isSetAddress:").append(request.isSetAddress()).append("\n");
            result.append("isSetAccount:").append(request.isSetAccount()).append("\n");
            result.append("----------").append("\n");
            result.append("Name:").append(request.getName())
                    .append(", OldName:").append(request.getOldName())
                    .append(", Age:").append(request.getAge())
                    .append(", Address:").append(request.getAddress())
                    .append(", Account:").append(request.getAccount());
        } else {
            result.append("Now is ").append(new Date().toLocaleString());
        }
        System.out.println(result.toString());
        return result.toString();
    }
}

服务端启动服务

上面这个就是服务端的具体实现类,现在需要启动这个服务,所以需要一个启动类,启动类的代码如下:

代码主要实现需要做:

  1. 实例化服务端,并且关联服务的实现类HelloWordServiceImpl
  2. 创建server类,关联协议工厂实例、传送方式实例(该实例中设置服务端口)、服务端实例
package com.zl.thrift;

import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;

public class ServerTest {
    private final static int port = 7456;

    public static void main(String[] args) throws Exception {
        /** 创建传输方式实例,并设置监听端口 */
        TServerSocket tServerSocket = new TServerSocket(port);

        /**
         * 实例化服务端对象,并关联Thrift文件中service具体实现类
         */
        HelloWordService.Processor<HelloWordServiceImpl> helloWordServiceProcessor =
                new HelloWordService.Processor<>(new HelloWordServiceImpl());

        /** 采用TCompactProtocol传输协议,服务端和客户端协议使用必须一致
         * 可选的协议有:TBinaryProtocol/TJSONProtocol/TSimpleJSONProtocol/TTupleProtocol
         * 其中TTupleProtocol是TCompactProtocol的子类,可以根据自己的业务选择对应的协议
         */
        TProtocolFactory pf = new TCompactProtocol.Factory();
        /**
         * 创建server实例,并设置传输方式,传输协议,服务端实例
         */
        TServer server =
                new TThreadPoolServer(new TThreadPoolServer.Args(tServerSocket).protocolFactory(pf).processor(helloWordServiceProcessor));
        System.out.println("server started ,port is " + port);
        server.serve();
    }
}

运行之后看到控制台的输出为:

server started ,port is 7456

客户端请求

现在服务已经启动,可以通过客户端向服务端发送请求了,客户端的代码如下:

代码主要的完成的工作为:

1、创建客户端实例,创建客户端需要用到传输协议(传输协议中需要关联传输方式)和传输方式实例(传输方式实例中关联服务端ip和port)

2、使用客户端实例取调用服务端的方法

package com.zl.thrift;

import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class ClientTest {
    public static void main(String[] args) throws Exception {
        /* 创建传输方式实例,设置服务端ip和port */
        TTransport transport = new TSocket("localhost", 7456);
        /* 创建传输协议实例,并设置传输方式 */
        TProtocol protocol = new TCompactProtocol(transport);
        /* 创建client,关联传输协议 */
        com.zl.thrift.HelloWordService.Client client = new com.zl.thrift.HelloWordService.Client(protocol);
        /* 建立连接 */
        transport.open();
        // 第一种请求类型
        com.zl.thrift.Request request = new com.zl.thrift.Request();
        request.setType(com.zl.thrift.RequestType.SAY_HELLO);
        request.setName("zhangsan");
        // 通过客户端实例调用服务端实现类中的方法
        System.out.println(client.doAction(request));

    }
}

运行客户端代码,得到结果:

Received 1

isSetName:true

isSetOldName:false

isSetAge:true

isSetAddress:false

isSetAccount:false

----------

Name:zhangsan, OldName:null, Age:0, Address:null, Account:0并且此时,服务端会有请求日志:

Get request: Request(type:SAY_HELLO, name:zhangsan, oldName:null, age:0)

isSetName:true

isSetOldName:false

isSetAge:true

isSetAddress:false

isSetAccount:false

----------

Name:zhangsan, OldName:null, Age:0, Address:null, Account:0

可以看到,客户端成功将请求发到了服务端,服务端成功地将请求结果返回给客户端,整个通信过程完成。

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值