精华内容
下载资源
问答
  • Thrift

    2021-06-22 20:58:38
    Thrift

    Thrift

    thrift主要用于各个服务之间的RPC通信,支持跨语言。thrift是一个典型的CS结构,客户端和服务端可以使用不同的语言开发,thrift通过IDL(Interface Description Language)来关联客户端和服务端。thrift的整体架构图如下图所示

    展开全文
  • thrift

    2021-06-11 13:56:30
    摘要:tong'xin'shuan thrift是一种序列化协议,如何理解序列化,网络数据传输是基于二进制的,二进制是无规则的.那么如何将二进制流识别成我们可以阅读的格式,或者说

    摘要:

    thrift是一种序列化协议,如何理解序列化,网络数据传输是基于二进制的,二进制是无规则的.那么如何将二进制流识别成我们可以阅读的格式,或者说通信双方如何互相知道对方发送的二进制流究竟是个什么东西.  计算机世界里可不像人眼一样,它是一个死脑筋,并不知道二进制流表达的是什么,除非...你告诉它.  假设A  B通信,双方在通信开始之前先进行规则的约束,  比如前三个字节表示一个字符串,第四个字节表示换行符.  约束好之后,双方根据约束的规则进行通信,这样就能解析了.  而这个约束的规则就是序列化.   现在前后端交互用的最多的序列化格式就是json,json实际上就是一种传输约束,双方通信都采用json的格式,json数据的传输可以跨平台,跨语言是最常见的一种序列化方式.

    thrift也是一种序列化约束,顾名思义也可以进行数据的识别定义.  thrift是一种与语言无关的一种传输规则的约定,不管是什么需要,只要在通信之前用thrift生成对象描述,那么这个对象就可以在thrift的客户端服务端进行该对象的传输,该对象的包装以及解包装都由thrift帮我们完成, 我们要做的就是在客户端和服务端对要传输的对象进行同样的规则约束,对thrift的来说,规则的约束也可以理解成对要传输的对象的定义, 即通信双方对要传输的对象的定义要保持一致.    thrift的对象的定义是借助".thrift"文件进行描述的  ,这个文件中可以进行对象的描述. 然后将这个文件分发给通信的双方,双方利用这个文件(假设文件名字是test.thrift)运行 thrift --gen java/py  test.thrift,  执行这个命令之后, 就会生成对象的对象代码,这是thrift自动帮我们生成的,  我们在传输的时候就可以操作这些对象进行数据传输.  除了可以在test.thrift定义对象进行数据传输,thrift也支持基本数据类型的传输,比如writeString(),readString(),  客户端writeString, 服务端readString()就可以进行数据的传输,我们不需要关心粘包,问题,这些thrift自动帮我们去做.

    一:socket粘包

    说来说去,socket又是不得不提, 数据在网络上的传输无非就是TCP/UDP传输,这里主要说socket. 网络数据的传输是二进制流,通信双方建立socket链接之后就可以传输数据了, TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息。而且socket什么时候开始发送数据数据也是自动的,正常来说缓冲区满了就可以进行发送了,所以socket对数据来说就是一个通道,  消息如何发送何时发送由socket控制.  因为socket默认情况下是无界的,所谓的无界就是说第一条消息和第二条消息之间我们人类可以知道,但是计算机不知道,所以客户端无法还原 成一条一条的数据,因为是无界的.  除非在传输数据的时候哭护短服务端约定好每条消息用多少字节发送, 就可以解决粘包.    解决粘包的思想就是在消息之间定义一种描述,让通信双发之后消息和消息之间的边界是什么,有大概以下几种思路.

    1.强制约定每天消息的大小:发送方和接收方规定固定大小的缓冲区,也就是发送和接收都使用固定大小的 byte[] 数组长度,当字符长度不够时使用空字符弥补;
    2.采用消息头+消息体的思路:在 TCP 协议的基础上封装一层数据请求协议,既将数据包封装成数据头(存储数据正文大小)+ 数据正文的形式,这样在服务端就可以知道每个数据包的具体长度了,知道了发送数据的具体边界之后,就可以解决半包和粘包的问题了;(比较常用)
       也就是每条消息分两次发送: 第一次发送一个四个字节的数字,这个数字代表接下来要发送的消息的字节长度. client.put(byte[4] b)  ,第二次发送数据:client.put(byte[] message)
      服务端每次接收分两次接收:第一次接受四个字节,len=server.receive(byte[4] b), 然后接收这个长度len的message ,server.receive(byte[len] message)
    3.以特殊的字符结尾,比如以“\n”结尾,这样我们就知道结束字符,从而避免了半包和粘包问题(推荐解决方案)。

    三:为什么需要thrift

    通过上面的了解对序列化/反序列化 ,以及socket粘包的知识了解之后,再来说thrift,  thrift定义了数据的传输对象的描述,既然有了描述,那么自然知道每条数据的边界了,所以thrift用于socket数据传输,我们就不需要考虑粘包的问题了,thrift会为我们自动拆包解析出来发送的数据.   

      其实到这里已经学到了很多东西啦, 想象一下,既然thrift只是一种描述语言,那么肯定不是唯一的序列化传输协议.  是的,确实是这样,有很多很多.  说白了描述语言就是一种定义在socket基础上的传输协议,有了这个协议就可以愉快的在网络上传输数据了,再也不用考虑数据无法识别,也不用考虑消息边界啦.  多说一句thrift除了可以用于序列化传输,也提供了rpc的功能,这也是它的一大特性. 

    四:下面是一个数据传输用thrift的简单demo

    这个demo基于我的父子进程通信的文章:https://mp.csdn.net/editor/html/117523496

    首先在项目中引入thrift的jar包用于开发:

    <dependency>
        <groupId>org.apache.thrift</groupId>
        <artifactId>libthrift</artifactId>
        <version>0.14.1</version>
    </dependency>
    package connect.test2;
    
    import org.apache.thrift.TException;
    import org.apache.thrift.protocol.TBinaryProtocol;
    import org.apache.thrift.protocol.TProtocol;
    import org.apache.thrift.transport.TIOStreamTransport;
    import org.apache.thrift.transport.TTransportException;
    
    import java.io.*;
    //其实System.out是一个流对象:源码:public class PrintStream extends FilterOutputStream
    //System.out.println("")其实是PrintStream.print()
    public class Child {
        public static void main(String[] args) throws IOException, InterruptedException, TException {
    
            TIOStreamTransport in = new TIOStreamTransport(System.in);
            TProtocol inProtocol = new TBinaryProtocol(in);
            String str = inProtocol.readString();
    
            TIOStreamTransport out = new TIOStreamTransport(System.out);
            TProtocol outProtocol = new TBinaryProtocol(out);
    
            outProtocol.writeString("hi>>" + str);
    
            outProtocol.writeFieldStop();
    
        }
    
    }
    
    
    
    
    package connect.test2;
    
    import org.apache.thrift.TException;
    import org.apache.thrift.protocol.TBinaryProtocol;
    import org.apache.thrift.protocol.TJSONProtocol;
    import org.apache.thrift.protocol.TProtocol;
    import org.apache.thrift.transport.TIOStreamTransport;
    import org.apache.thrift.transport.TSimpleFileTransport;
    import org.apache.thrift.transport.TTransportException;
    
    import java.io.*;
    //process.getInputStream是用来读取控制台命令结果的   控制台数据的流入可以往System.out
    //process.getOutputStream是用来往控制台写入数据的   此结果可以由System.in端读出
    public class Father {
        public static void main(String[] args) throws IOException, TException {
            Runtime run = Runtime.getRuntime();
    //      获取java执行命令所在的位置
            String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
    //      获取类加载的时候的加载的class文件的路径,返回结果是一个string,多个路径之间用";"分割.
            String cp = "\"" + System.getProperty("java.class.path");
    //      获取当前程序的class文件所在的位置
            cp += File.pathSeparator + ClassLoader.getSystemResource("").getPath() + "\"";
    //        java -cp 命令   (这是运行class文件的命令,会将class文件载入jvm)
            String cmd = java + " -cp " + cp + " connect.test2.Child";
    //        开启一个子进程
            Process child_process = run.exec(cmd);
    //      往子进程写数据
            TIOStreamTransport tIOStreamTransportnew = new TIOStreamTransport(child_process.getOutputStream());
            TBinaryProtocol tBinaryProtocol = new TBinaryProtocol(tIOStreamTransportnew);
            tBinaryProtocol.writeString("hello world");
            tIOStreamTransportnew.open();
    
            System.out.println("写出成功");
            tIOStreamTransportnew.close();
    //     父进程获取子进程控制台数据
            TIOStreamTransport in = new TIOStreamTransport(child_process.getInputStream());
            TBinaryProtocol inputProtocol = new TBinaryProtocol(in);
            in.open();
            String str = inputProtocol.readString();
            System.out.println("father receive:"+str);
    
    
    
        }
    }
    //启动father,  father会自动启动child,然后二者进行了通信,数据传输用的是thrift的协议.
    之前说过thrift除了可以自定义对象描述,也支持一些基本的数据类型,对这些基本类型可以直接传输,
    thrift的序列化会识别消息边界.这里readString() ,writeString("xxx");必须成对出现,这样就可以传输数据了.

     

    五:thrift支持的数据类型

    thrift描述文件定义的数据结构是跨语言的,和平台无关的,既然如此那么thrift自然有自己的数据结构.

        5预定义的基本类型
           bool  布尔类型
           byte  单字节类型
           i16   有符号的十六位整数
           i32   有符号的32位整数
           i64   有符号的64位整数
           double 64位的浮点小数
           binarry 字节数组
           string 字符串
        5.2.用户定义的结构体(Structs)
           thrift 的structs在概念上类似于c++的struct,是一种便捷的分组封装方式,一种结构. thrift的structs会被转化成面向对象语言的class类结构.
               struct MyStruct{
                  
               
                 }
        5.3.容器(集合)类型(List Set Map)
           thrift容器是强类型容器,它映射到流行编程语言中最常用的容器。它们使用Java泛型风格进行注释。
            list<t1>:有序类型的元素列表t1。可能包含重复项。
            set<t1>:类型为t1的唯一元素的无序集合。
            map<T1,T2> :T1类型的值T2。
        5.4.异常类型(Exceptions)
           异常类型的定义和struct基本等价,只不过异常使用关字:
            exception{
            
              }
        5.5.服务类型(Services)
              Services在语义上等同于面向对象编程中定义接口(或纯虚拟抽象类)。Thrift编译器会根据定义的接口生成实现接口实例. 用于RPC

    六:thrift的常量:


        Thrift允许您定义跨语言使用的常量。复杂类型和结构可以使用JSON表示法指定的(支持十六进制值)。
        const i32 INT_CONST = 1234;    // 1
        const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}

    七:定义一个struc

       #注意个定义末尾要记得";"分号
        #每个类型的定义前面必须有一个独一无二的正整数标识符,比如下面的1, 2, 3, 4, 16
        #字段可以被声明为required(必须) 或者optional(可选),
        #struct之间可以互相引用依赖
        #可以给生命字段定义一个默认值
        #可以在同一个 Thrift 文件中定义和引用多个struct

        struct Location {                            
           1: required double latitude;
           2: required double longitude;
          }
        struct Tweet {
           1: required i32 userId;                  
           2: required string userName;             
           3: required string text;
           4: optional Location loc;                
           16: optional string language = "english"     
          }
          如您所见,消息定义中的每个字段都有一个唯一的正整数编号标签。这些标签用于标识字段,一旦您的消息类型被使用,就不应更改。
          字段可以标记为required(必需)或optional(可选)。例如,如果未在结构中设置required(必填)的值,Thrift 会报错。如果未在结构中设置可选字段,则不会通过网络对其进行序列化。如果为可选字段指定了默认值,则在解析结构体时为该字段分配默认值,并且没有为该字段显式分配值。
    与服务不同,结构体不支持继承,即一个结构体不能扩展其他结构体。

    八:定义service

       虽然市面上有一些流行的序列化/反序列化框架 比如google的Protocol Buffers,但是很少有框架提供开箱即用的跨多种语言的RPC服务,这也是thrift的一个主要吸引点.
       可以将service的定义想象成java  接口的定义那样--你需要提供一个方法名字和方法签名,而且service可以继承其他的service.  thrift的编译器可以根据你选择的语言和你的service的定义自动生成
       对应的服务端代码和客户端的代码.Thrift为大多数语言提供了RPC库,您可以使用它来运行您的客户端和服务器.
          service Twitter {
                // 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 list are specified using the exact same syntax as
                // field lists in structs.
                void ping(),                                                             // 1 方法和方法之间可以用逗号或者是分号区分
                bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable), // 2 参数可以是简单类型或者复杂结构体
                TweetSearchResult searchTweets(1:string query);                          // 3 返回类型可以是简单类型或者复杂结构体

                // The 'oneway' modifier indicates that the client only makes a request and
                // does not wait for any response at all. Oneway methods MUST be void.
                oneway void zip()                                                        // 4 service 可以用extends 建立继承关系
            }

    九:thrift的传输概念的介绍

        9.1:transport

                transport定义了数据传输,有各种各样的数据传输流,有网络流文件流等.

     

         

           

    展开全文
  • 由浅入深了解Thrift(一)——Thrift介绍与用法

    万次阅读 多人点赞 2015-01-16 16:27:17
    一、Thrift简单介绍 1.1、 Thrift是什么?能做什么? Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式;用户通过Thrift的IDL(接口定义语言)来描述接口函数及...

    相关示例代码见:http://download.csdn.net/detail/hjx_1000/8374829

    一、  Thrift简单介绍

    1.1、  Thrift是什么?能做什么?

    Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式;用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。

    例如,我想开发一个快速计算的RPC服务,它主要通过接口函数getInt对外提供服务,这个RPC服务的getInt函数使用用户传入的参数,经过复杂的计算,计算出一个整形值返回给用户;服务器端使用java语言开发,而调用客户端可以是java、c、python等语言开发的程序,在这种应用场景下,我们只需要使用Thrift的IDL描述一下getInt函数(以.thrift为后缀的文件),然后使用Thrift的多语言编译功能,将这个IDL文件编译成C、java、python几种语言对应的“特定语言接口文件”(每种语言只需要一条简单的命令即可编译完成),这样拿到对应语言的“特定语言接口文件”之后,就可以开发客户端和服务器端的代码了,开发过程中只要接口不变,客户端和服务器端的开发可以独立的进行。

    Thrift为服务器端程序提供了很多的工作模式,例如:线程池模型、非阻塞模型等等,可以根据自己的实际应用场景选择一种工作模式高效地对外提供服务;

    1.2、  Thrift的相关网址和资料:

    (1)  Thrift的官方网站:http://thrift.apache.org/

    (2)  Thrift官方下载地址:http://thrift.apache.org/download

    (3)  Thrift官方的IDL示例文件(自己写IDL文件时可以此为参考):

    https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=test/ThriftTest.thrift;hb=HEAD

    (4)  各种环境下搭建Thrift的方法:

    http://thrift.apache.org/docs/install/

    该页面中共提供了CentOS\Ubuntu\OS X\Windows几种环境下的搭建Thrift环境的方法。

     

    二、  Thrift的使用

    Thrift提供跨语言的服务框架,这种跨语言主要体现在它对多种语言的编译功能的支持,用户只需要使用IDL描述好接口函数,只需要一条简单的命令,Thrift就能够把按照IDL格式描述的接口文件翻译成各种语言版本。其实,说搭建Thrift环境的时候,实际上最麻烦的就是搭建Thrift的编译环境,Thrift的编译和通常的编译一样经过词法分析、语法分析等等最终生成对应语言的源码文件,为了能够支持对各种语言的编译,你需要下载各种语言对应的编译时使用的包;

    2.1、  搭建Thrift的编译环境

    本节主要介绍如何搭建Unix编译环境,搭建时有以下要求:

    基本要求:

    G++、boost、lex、yacc

    源码安装要求:

    如果使用源码安装的方式,则还需要下列工具:

    Autoconf、automake、libtool、pkg-config、lex和yacc的开发版、libssl-dev

    语言要求:

    搭建C++编译环境:boost、libevent、zlib

    搭建java编译环境:jdk、ApacheAnt

    具体搭建环境时可以参考“一”中所列官网的安装方法。

    2.2、  搭建JAVA下Thrift开发环境

    在java环境下开发thrift的客户端或者服务器程序非常简单,只需在工程文件中加上下面三个jar包(版本可能随时有更新):

      libthrift-0.9.1.jar

      slf4j-api-1.7.5.jar

                 slf4j-simple.jar

    2.3、  编写IDL文件

    使用Thrift开发程序,首先要做的事情就是使用IDL对接口进行描述, 然后再使用Thrift的多语言编译能力将接口的描述文件编译成对应语言的版本,本文中将IDL对接口的描述文件称为“Thrift文件”。

    (1)  编写Thrift文件

    使用IDL对接口进行描述的thrift文件命名一般都是以“.thrift”作为后缀:XXX.thrift,可以在该文件的开头为该文件加上命名空间限制,格式为:namespace语言 命名空间的名字;例如:

    namespace javacom.test.service

    IDL文件中对所有接口函数的描述都放在service中,service的名字可以自己指定,该名字也将被用作生成的特定语言接口文件的名字,接口函数需要对参数使用序号标号,除最后一个接口函数外,要以“,”结束对函数的描述。

    例如,下面一个IDL描述的Thrift文件(该Thrift文件的文件名为:test_service.thrift)的全部内容:

     

    namespace java com.test.service
    
    include "thrift_datatype.thrift"
    
    service TestThriftService
    {
    
    	/**
    	*value 中存放两个字符串拼接之后的字符串
    	*/
    	thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),
    	
    	thrift_datatype.ResultInt getInt(1:i32 val)
    	
    }
    

    代码2.1

     

     

    这里的TestThriftService就被用作生成的特定语言的文件名,例如我想用该Thrift文件生成一个java版本的接口文件,那么生成的java文件名就是:TestThriftService.java。

    (1)  编写IDL文件时需要注意的问题

    [1]函数的参数要用数字依序标好,序号从1开始,形式为:“序号:参数名”;

    [2]每个函数的最后要加上“,”,最后一个函数不加;

    [3]在IDL中可以使用/*……*/添加注释

    (2)  IDL支持的数据类型

    IDL大小写敏感,它共支持以下几种基本的数据类型:

    [1]string, 字符串类型,注意是全部小写形式;例如:string aString

    [2]i16, 16位整形类型,例如:i16 aI16Val;

    [3]i32,32位整形类型,对应C/C++/java中的int类型;例如:      I32  aIntVal

    [4]i64,64位整形,对应C/C++/java中的long类型;例如:I64 aLongVal

    [5]byte,8位的字符类型,对应C/C++中的char,java中的byte类型;例如:byte aByteVal

    [6]bool, 布尔类型,对应C/C++中的bool,java中的boolean类型; 例如:bool aBoolVal

    [7]double,双精度浮点类型,对应C/C++/java中的double类型;例如:double aDoubleVal

    [8]void,空类型,对应C/C++/java中的void类型;该类型主要用作函数的返回值,例如:void testVoid(),

    除上述基本类型外,ID还支持以下类型:

    [1]map,map类型,例如,定义一个map对象:map<i32, i32> newmap;

    [2]set,集合类型,例如,定义set<i32>对象:set<i32> aSet;

    [3]list,链表类型,例如,定义一个list<i32>对象:list<i32> aList;

    (3)  在Thrift文件中自定义数据类型

    在IDL中支持两种自定义类型:枚举类型和结构体类型,具体如下:

    [1]enum, 枚举类型,例如,定义一个枚举类型:

    enum Numberz
    {
      ONE = 1,
      TWO,
      THREE,
      FIVE = 5,
      SIX,
      EIGHT = 8
    }

    注意,枚举类型里没有序号

     

     

    [2]struct,自定义结构体类型,在IDL中可以自己定义结构体,对应C中的struct,c++中的struct和class,java中的class。例如:

     

    struct TestV1 {
           1: i32 begin_in_both,
           3: string old_string,
           12: i32 end_in_both
    }

    注意,在struct定义结构体时需要对每个结构体成员用序号标识:“序号: ”。

    (4)  定义类型别名

    Thrift的IDL支持C/C++中类似typedef的功能,例如:

    typedefi32  Integer 

    就可以为i32类型重新起个名字Integer。

    2.4、  生成Thrift服务接口文件

    搭建Thrift编译环境之后,使用下面命令即可将IDL文件编译成对应语言的接口文件:

    thrift --gen <language> <Thrift filename>

    例如:如果使用上面的thrift文件(见上面的代码2.1):test_service.thrift生成一个java语言的接口文件,则只需在搭建好thrift编译环境的机子上,执行如下命令即可:

    thrift --gen java test_service.thrift

    这里,我直接在test_service.thrift文件所在的目录下执行的命令,所以直接使用文件名即可(如图2.1的标号1所示),如果不在test_service.thrift所在的目录中,则需要具体指明该文件所在的路径。

    图2.1 

    如图2.1 中标号2所示,生成的gen-java的目录,目录下面有com、test、service三级目录,这三级目录也是根据test_service.thrift文件中命名空间的名字:com.test.service生成的,进入目录之后可以看到生成的java语言的接口文件名为:TestThriftService.java,这个文件的名字也是根据test_service.thrift文件的service名字来生成的(见代码2.1)。

    2.5、  编写服务器端的java代码

    编写thrift服务器程序需要首先完成下面两步工作:

    (1)先将2.2节中的三个jar包添加到工程里,如图2.2的标号2所示。

    (2)将生成的java接口文件TestThriftService.java拷贝到自己的工程文件中,如图2.2的标号1所示。

    图2.2

    服务端程序需实现TestThriftService.Iface接口,在实现接口中完成自己要提供的服务,服务器端对服务接口实现的代码如下所示:

     

    package com.test.service;
    
    import org.apache.thrift.TException;
    
    public class TestThriftServiceImpl implements TestThriftService.Iface
    {
    
    	@Override
    	public String getStr(String srcStr1, String srcStr2) throws TException {
    		
    		long startTime = System.currentTimeMillis();
    		String res = srcStr1 + srcStr2; 
    		long stopTime = System.currentTimeMillis();
    		
    		System.out.println("[getStr]time interval: " + (stopTime-startTime));
    		return res;
    	}
    
    	@Override
    	public int getInt(int val) throws TException {
    		long startTime = System.currentTimeMillis();
    		int res = val * 10; 
    		long stopTime = System.currentTimeMillis();
    		
    		System.out.println("[getInt]time interval: " + (stopTime-startTime));
    		return res;
    	}
    
    }
    

    代码2.2

    服务器端启动thrift服务框架的程序如下所示,在本例中服务器采用TNonblockingServer工作模式:

     

    package com.test.service;
    import org.apache.thrift.TProcessor;
    import org.apache.thrift.protocol.TBinaryProtocol;
    import org.apache.thrift.server.TNonblockingServer;
    import org.apache.thrift.server.TServer;
    import org.apache.thrift.transport.TFramedTransport;
    import org.apache.thrift.transport.TNonblockingServerSocket;
    import org.apache.thrift.transport.TTransportException;
    public class testMain {
    	private static int m_thriftPort = 12356;
    	private static TestThriftServiceImpl m_myService = new TestThriftServiceImpl();
    	private static TServer m_server = null;
    	private static void createNonblockingServer() throws TTransportException
    	{
    		TProcessor tProcessor = new TestThriftService.Processor<TestThriftService.Iface>(m_myService);
    		TNonblockingServerSocket nioSocket = new TNonblockingServerSocket(m_thriftPort);
    		TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(nioSocket);
    		tnbArgs.processor(tProcessor);
    		tnbArgs.transportFactory(new TFramedTransport.Factory());
    		tnbArgs.protocolFactory(new TBinaryProtocol.Factory());
    		// 使用非阻塞式IO,服务端和客户端需要指定TFramedTransport数据传输的方式
    		m_server = new TNonblockingServer(tnbArgs);
    	}
    	public static boolean start()
    	{
    		try {
    			createNonblockingServer();
    		} catch (TTransportException e) {
    			System.out.println("start server error!" + e);
    			return false;
    		}
    		System.out.println("service at port: " + m_thriftPort);
    		m_server.serve();
    		return true;
    	}
    	public static void main(String[] args)
    	{
    		if(!start())
    		{
    			System.exit(0);
    		}
    	}
    	
    }
    

    代码2.3

    在服务器端启动thrift框架的部分代码比较简单,不过在写这些启动代码之前需要先确定服务器采用哪种工作模式对外提供服务,Thrift对外提供几种工作模式,例如:TSimpleServer、TNonblockingServer、TThreadPoolServer、TThreadedSelectorServer等模式,每种服务模式的通信方式不一样,因此在服务启动时使用了那种服务模式,客户端程序也需要采用对应的通信方式。

    另外,Thrift支持多种通信协议格式:TCompactProtocol、TBinaryProtocol、TJSONProtocol等,因此,在使用Thrift框架时,客户端程序与服务器端程序所使用的通信协议一定要一致,否则便无法正常通信。

    以上述代码2.3采用的TNonblockingServer为例,说明服务器端如何使用Thrift框架,在服务器端创建并启动Thrift服务框架的过程为:

    [1]为自己的服务实现类定义一个对象,如代码2.3中的:

    TestThriftServiceImplm_myService =newTestThriftServiceImpl();

    这里的TestThriftServiceImpl类就是代码2.2中我们自己定义的服务器端对各服务接口的实现类。

    [2]定义一个TProcess对象,在根据Thrift文件生成java源码接口文件TestThriftService.java中,Thrift已经自动为我们定义了一个Processor;后续节中将对这个TProcess类的功能进行详细描述;如代码2.3中的:

    TProcessor tProcessor = NewTestThriftService.Processor<TestThriftService.Iface>(m_myService);

    [3]定义一个TNonblockingServerSocket对象,用于tcp的socket通信,如代码2.3中的:

    TNonblockingServerSocketnioSocket = newTNonblockingServerSocket(m_thriftPort);

    在创建server端socket时需要指明监听端口号,即上面的变量:m_thriftPort

    [4]定义TNonblockingServer所需的参数对象TNonblockingServer.Args;并设置所需的参数,如代码2.3中的:

    TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(nioSocket);
    tnbArgs.processor(tProcessor);
    tnbArgs.transportFactory(new TFramedTransport.Factory());
    tnbArgs.protocolFactory(new TBinaryProtocol.Factory());
    

    在TNonblockingServer模式下我们使用二进制协议:TBinaryProtocol,通信方式采用TFramedTransport,即以帧的方式对数据进行传输。

    [5]定义TNonblockingServer对象,并启动该服务,如代码2.3中的:

    m_server = new TNonblockingServer(tnbArgs);

    m_server.serve();

    2.6、  编写客户端代码

    Thrift的客户端代码同样需要服务器开头的那两步:添加三个jar包和生成的java接口文件TestThriftService.java。

    		 m_transport = new TSocket(THRIFT_HOST, THRIFT_PORT,2000);
    		 TProtocol protocol = new TBinaryProtocol(m_transport);
    		 TestThriftService.Client testClient = new TestThriftService.Client(protocol);
    		
    		 try {
    			 m_transport.open();
    			 
    			 String res = testClient.getStr("test1", "test2");
    			 System.out.println("res = " + res);
    			 m_transport.close();
    		} catch (TException e){
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    		}

    代码2.4

    由代码2.4可以看到编写客户端代码非常简单,只需下面几步即可:

    [1]创建一个传输层对象(TTransport),具体采用的传输方式是TFramedTransport,要与服务器端保持一致,即:

    m_transport =new TFramedTransport(newTSocket(THRIFT_HOST,THRIFT_PORT, 2000));

    这里的THRIFT_HOST, THRIFT_PORT分别是Thrift服务器程序的主机地址和监听端口号,这里的2000是socket的通信超时时间;

    [2]创建一个通信协议对象(TProtocol),具体采用的通信协议是二进制协议,这里要与服务器端保持一致,即:

    TProtocolprotocol =new TBinaryProtocol(m_transport);

    [3]创建一个Thrift客户端对象(TestThriftService.Client),Thrift的客户端类TestThriftService.Client已经在文件TestThriftService.java中,由Thrift编译器自动为我们生成,即:

    TestThriftService.ClienttestClient =new TestThriftService.Client(protocol);

    [4]打开socket,建立与服务器直接的socket连接,即:

    m_transport.open();

    [5]通过客户端对象调用服务器服务函数getStr,即:

    String res = testClient.getStr("test1","test2");

                    System.out.println("res = " +res);

    [6]使用完成关闭socket,即:

    m_transport.close();

            这里有以下几点需要说明:

    [1]在同步方式使用客户端和服务器的时候,socket是被一个函数调用独占的,不能多个调用同时使用一个socket,例如通过m_transport.open()打开一个socket,此时创建多个线程同时进行函数调用,这时就会报错,因为socket在被一个调用占着的时候不能再使用;

    [2]可以分时多次使用同一个socket进行多次函数调用,即通过m_transport.open()打开一个socket之后,你可以发起一个调用,在这个次调用完成之后,再继续调用其他函数而不需要再次通过m_transport.open()打开socket;

    2.7、  需要注意的问题

    (1)Thrift的服务器端和客户端使用的通信方式要一样,否则便无法进行正常通信;

    Thrift的服务器端的种模式所使用的通信方式并不一样,因此,服务器端使用哪种通信方式,客户端程序也要使用这种方式,否则就无法进行正常通信了。例如,上面的代码2.3中,服务器端使用的工作模式为TNonblockingServer,在该工作模式下需要采用的传输方式为TFramedTransport,也就是在通信过程中会将tcp的字节流封装成一个个的帧,此时就需要客户端程序也这么做,否则便会通信失败。出现如下问题:

    服务器端会爆出如下出错log:

    2015-01-06 17:14:52.365 ERROR [Thread-11] [AbstractNonblockingServer.java:348] - Read an invalid frame size of -2147418111. Are you using TFramedTransport on the client side?

    客户端则会报出如下出错log:

    org.apache.thrift.transport.TTransportException: java.net.SocketException: Connection reset
    	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:129)
    	at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84)
    	at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:362)
    	at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:284)
    	at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:191)
    	at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:69)
    	at com.browan.freepp.dataproxy.service.DataProxyService$Client.recv_addLiker(DataProxyService.java:877)
    	at com.browan.freepp.dataproxy.service.DataProxyService$Client.addLiker(DataProxyService.java:862)
    	at com.browan.freepp.dataproxy.service.DataProxyServiceTest.test_Likers(DataProxyServiceTest.java:59)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:606)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: java.net.SocketException: Connection reset
    	at java.net.SocketInputStream.read(SocketInputStream.java:196)
    	at java.net.SocketInputStream.read(SocketInputStream.java:122)
    	at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
    	at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)
    	at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
    	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:127)
    	... 31 more

    (2)在服务器端或者客户端直接使用IDL生成的接口文件时,可能会遇到下面两个问题:

    [1]、Cannotreduce the visibility of the inherited method fromProcessFunction<I,TestThriftService.getStr_args>

    [2]、The typeTestThriftService.Processor<I>.getStr<I> must implement theinherited abstract methodProcessFunction<I,TestThriftService.getStr_args>.isOneway()

    问题产生的原因:

    问题[1] 是继承类的访问权限缩小所造成的;

    问题[2] 是因为存在抽象函数isOneWay所致;

    解决办法:

    问题[1]的访问权限由protected修改为public;

    问题[2]的解决办法是为抽象函数添加一个空的函数体即可。

    2.8、  应用技巧

    (1)  为调用加上一个事务ID

    在分布式服务开发过程中,一次事件(事务)的执行可能跨越位于不同机子上多个服务程序,在后续维护过程中跟踪log将变得非常麻烦,因此在系统设计的时候,系统的一个事务产生之处应该产生一个系统唯一的事务ID,该ID在各服务程序之间进行传递,让一次事务在所有服务程序输出的log都以此ID作为标识。

    在使用Thrift开发服务器程序的时候,也应该为每个接口函数提供一个事务ID的参数,并且在服务器程序开发过程中,该ID应该在内部函数调用过程中也进行传递,并且在日志输出的时候都加上它,以便问题跟踪。

    (2)  封装返回结果

    Thrift提供的RPC方式的服务,使得调用方可以像调用自己的函数一样调用Thrift服务提供的函数;在使用Thrift开发过程中,尽量不要直接返回需要的数据,而是将返回结果进行封装,例如上面的例子中的getStr函数就是直接返回了结果string,见Thrift文件test_service.thrift中对该函数的描述:

    stringgetStr(1:string srcStr1, 2:string srcStr2)

    在实际开发过程中,这是一种很不好的行为,在返回结果为null的时候还可能造成调用方产生异常,需要对返回结果进行封装,例如:

    /*String类型返回结果*/
    struct ResultStr
    {
      1: ThriftResult result,
      2: string value
    }
    

    其中ThriftResult是自己定义的枚举类型的返回结果,在这里可以根据自己的需要添加任何自己需要的返回结果类型:

    enum ThriftResult
    {
      SUCCESS,           /*成功*/
      SERVER_UNWORKING,  /*服务器处于非Working状态*/
      NO_CONTENT,  		 /*请求结果不存在*/
      PARAMETER_ERROR,	 /*参数错误*/
      EXCEPTION,	 	 /*内部出现异常*/
      INDEX_ERROR,		 /*错误的索引或者下标值*/
      UNKNOWN_ERROR, 	 /*未知错误*/
      DATA_NOT_COMPLETE, 	 /*数据不完全*/
      INNER_ERROR, 	 /*内部错误*/
    }
    

     

    此时可以将上述定义的getStr函数修改为:

    ResultStr  getStr(1:string srcStr1, 2:string srcStr2)

    在此函数中,任何时候都会返回一个ResultStr对象,无论异常还是正常情况,在出错时还可以通过ThriftResult返回出错的类型。

    (3)  将服务与数据类型分开定义

    在使用Thrift开发一些中大型项目的时候,很多情况下都需要自己封装数据结构,例如前面将返回结果进行封装的时候就定义了自己的数据类型ResultStr,此时,将数据结构和服务分开定义到不通的文件中,可以增加thrift文件的易读性。例如:

    在thrift文件:thrift_datatype.thrift中定义数据类型,如:

    namespace java com.browan.freepp.thriftdatatype
    const string VERSION = "1.0.1"
    /**为ThriftResult添加数据不完全和内部错误两种类型
    */
    
    /****************************************************************************************************
    * 定义返回值,
    * 枚举类型ThriftResult,表示返回结果,成功或失败,如果失败,还可以表示失败原因
    * 每种返回类型都对应一个封装的结构体,该结构体其命名遵循规则:"Result" + "具体操作结果类型",结构体都包含两部分内容:
    * 第一部分为枚举类型ThriftResult变量result,表示操作结果,可以 表示成功,或失败,失败时可以给出失败原因
    * 第二部分的变量名为value,表示返回结果的内容;
    *****************************************************************************************************/
    enum ThriftResult
    {
      SUCCESS,           /*成功*/
      SERVER_UNWORKING,  /*服务器处于非Working状态*/
      NO_CONTENT,  		 /*请求结果不存在*/
      PARAMETER_ERROR,	 /*参数错误*/
      EXCEPTION,	 	 /*内部出现异常*/
      INDEX_ERROR,		 /*错误的索引或者下标值*/
      UNKNOWN_ERROR 	 /*未知错误*/
      DATA_NOT_COMPLETE 	 /*数据不完全*/
      INNER_ERROR 	 /*内部错误*/
    }
    
    /*bool类型返回结果*/
    struct ResultBool 
    {
      1: ThriftResult result,
      2: bool value
    }
    
    /*int类型返回结果*/
    struct ResultInt
    {
      1: ThriftResult result,
      2: i32 value
    }
    
    /*String类型返回结果*/
    struct ResultStr
    {
      1: ThriftResult result,
      2: string value
    }
    
    /*long类型返回结果*/
    struct ResultLong
    {
      1: ThriftResult result,
      2: i64 value
    }
    
    
    
    /*double类型返回结果*/
    struct ResultDouble
    {
      1: ThriftResult result,
      2: double value
    }
    
    /*list<string>类型返回结果*/
    struct ResultListStr 
    {
      1: ThriftResult result,
      2: list<string> value
    }
    
    /*Set<string>类型返回结果*/
    struct ResultSetStr 
    {
      1: ThriftResult result,
      2: set<string> value
    }
    
    /*map<string,string>类型返回结果*/
    struct ResultMapStrStr 
    {
      1: ThriftResult result,
      2: map<string,string> value
    }
    

    代码2.5

    在另外一个文件test_service.thrift中定义服务接口函数,如下所示:

    namespace java com.test.service
    
    include "thrift_datatype.thrift"
    
    service TestThriftService
    {
    
    	/**
    	*value 中存放两个字符串拼接之后的字符串
    	*/
    	thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),
    	
    	thrift_datatype.ResultInt getInt(1:i32 val)
    	
    }
    

    代码 2.6

    由于在接口服务定义的thrift文件test_service.thrift中要用到对数据类型定义的thrift文件:thrift_datatype.thrift,因此需要在其文件前通过include把自己所使用的thrift文件包含进来,另外在使用其他thrift文件中定义的数据类型时要加上它的文件名,如:thrift_datatype.ResultStr

    (4)  为Thrift文件添加版本号

    在实际开发过程中,还可以为Thrift文件加上版本号,以方便对thrift的版本进行控制,如代码2.5所示。

     

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,069
精华内容 22,027
关键字:

thrift