初识Thrift

来源:互联网 发布:mac文件都在桌面 编辑:程序博客网 时间:2024/05/17 22:35

  • Thrift
    • Thrift接口描述文件
      • 注释
      • 基础类型
      • 容器
      • 结构体
      • 服务
      • 语法Example
    • Thrift的三个组件
      • protocol
      • transport
      • server
    • Thrift工具的使用
    • Python Examlple

本文为阅读总结,如果时间充裕,建议阅读参考的文章,Python Example为练习demo。

参考网址

  • http://thrift.apache.org/ 官网
  • http://blog.jobbole.com/107828/ 此处有Java Example
  • https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/ Java Example和环境搭建
  • http://dongxicheng.org/search-engine/thrift-guide/
  • http://www.cnblogs.com/cyfonly/p/6059374.html 最佳入门文章
  • http://blog.csdn.net/whycold/article/category/1251519 源码研究

Thrift

Thrift(Apache thrift)是一款软件开发RPC(Remote Procedure Call Protocol,远程过程调用协议)框架,可以高效地实现跨语言的RPC服务

Thrift 是IDL描述性语言的一个具体实现,适用于程序对程序静态的数据交换,但是需要提前确定好数据结构。

Thrift 是完全静态的,当数据结构发生变化时,必须重新编辑IDL文件、代码生成再编译载入的流程,这也是Thrift的弱项。Thrift是用于搭建大型数据交换及存储的通用工具,在大型系统中的内部数据传输上相对于JSON 和XML 无论在性能、传输大小上都有明显的优势。

Thrift 不仅仅是个高效的序列化工具,它是一个完整的 RPC 框架体系!

Thrift接口描述文件

在编写接口文件时,需要对要传输的数据设定数据类型。因为Thrift支持众多开发语言,所以Thrift提供了一套自己的数据类型编写规范,只有用这套独立于任何语言的类型规范来编写接口文件,Thrift才能把它转换成指定的那种开发语言。

Thrift中的类型包括基础类型、结构、容器、异常、服务等。

注释

单行注释,#

多行注释,/**/

基础类型

bool     布尔类型(truefalse)byte     8位有符号整数i16      16位有符号整数i32      32位有符号整数i64      64位有符号整数double   64位浮点数string   文本字符串,使用utf-8编码binary   二进制数据类型(字节数组)

容器

Thrift提供了三种最常用的容器

  1. list容器:一个元素可重复的有序列表。 会转换成C++中的vector,Java中的ArrayList,Python中的list等;

  2. set容器:一个元素不可重复的无序集合。会转换成C++中的set,Java中的HashSet、Python中的set等;

  3. map容器:一个含有多个key:value键值对的结构。会转换成C++中的map,Java中的HashMap,PHP中的关联数组,Python中的dict等。

对于上述三种容器,其元素的类型原则上可以是任何一种Thrift类型。但是map的key类型需要是基础类型,因为很多开发语言并不支持map的key类型为复杂数据类型,如Python中的键值必须为不可变对象。

结构体

在形式上和C/C++中的结构体类型非常相似,Thrift接口文件中的结构体类型,会被转换成一个独立的类(class)。类的属性便是结构体中的各个类型,而类的方法便是对这些类型进行处理的相关函数。

struct UserGradeInfo {1: required string UserName = "Anonymous";2: required i16 UserGrade = 0;}

结构体中每一个变量都有一个正整数标识符,这个标识符并不要求连续,但是一旦定义,不建议再进行修改。

另外,每个变量前都会有required或optional的限定,前者表示是必填域,后者则表示是可选域。域是可以有默认值的。

  1. 如果一个变量设置了required,但是在实际构造结构体时又没有给这个变量赋值,那么thrift会认为这是一个异常;
  2. 如果一个变量设置为optional,且在构造结构体时没有给这个变量赋值,thrift不会抛出异常。

服务

服务与面向对象中的接口类似。Thrift编译工具会根据服务的定义来产生相应的方法和函数。

每个服务,都包括了若干个函数,每个函数包括了若干参数和一个返回值(返回值可以是void)。

语法Example

# 例子 - thrift接口描述文件# 编写这个文件是为了教会你如何写thrift接口描述文件。# 第一个你应该掌握的知识点就是.thrift文件# 支持shell的注释方式,那就是用#符号。/***常用数据类型,如下所示:* bool 布尔型,1个字节* byte 有符号整数,1个字节* i16 有符号16位整型* i32 有符号32位整型* i64 有符号64位整型* double 64位浮点数值* string 字符串类型* binary 二进制数据类型(字节数组)* list 单类型有序列表,允许有重复元素* set 单类型无需集合,不允许有重复元素* map<t1,t2> Map型(key:value)** 你发现了么,.thrift文件还支持C语言的多行注释形式。*/// 不卖关子了,其实我们还支持C语言的单行注释形式呢 ^_^/*** .thrift文件可以引用其他.thrift文件,这样就可以方便地把一些公共结构和服务囊括进来。* 在引用其他.thrift文件时,既可以直接引用当前文件夹下的文件,也可以引用其他路径下的* 文件,但后者需要在thrift编译工具编译时加上-I选项来设定路径。** 如果希望访问被包含的.thrift文件中的内容,则需要使用.thrift文件的文件名作为前缀,* 比如shared.SharedObject。我们在本例中引用了文件shared.thrift。*/include "shared.thrift"/*** Thrift支持对.thrift文件中的类型设定namespace,这样可以有效避免名字冲突。* 这种机制在C++中也叫做namespace,而在Java中叫做Package。* thrift支持针对不同的语言设置不同的namespace,比如下面的例子。* thrift会在生成不同语言代码时,进行相应的设置。*/namespace cpp tutorialnamespace go tutorialnamespace java tutorialnamespace php tutorialnamespace perl tutorial/*** thrift还可以使用typedef来给类型起别名。*/typedef i32 MyInteger/*** Thrift也支持定义常量。* 对于结构复杂的常量,支持使用JSON形式来表示。*/const i32 MY_NUM = 9853const map<string,string> MY_MAP = {'hello':'world', 'goodnight':'moon'}/*** 你还可以定义枚举类型, 其被指定为32位整型。域的值是可以自定义的,而且* 当不提供域的值时,默认会从1开始编号并递增。*/enum Operation {ADD = 1,SUBTRACT = 2,MULTIPLY = 3,DIVIDE = 4}/*** 结构体则是一个复杂的数据类型。它由多个域组成,每个域会对应一个整数标识符,* 每一行的格式为:一个冒号,一个类型,一个域名称和一个(非必填的)默认值。** 每个域都可以设置为optional或required来表示是否为必填域,以便thrift决定是否* 在数据传输时要包含这个域。不指定时,默认为required。*/struct Work {1: i32 num1 = 0,2: i32 num2,3: Operation op,4: optional string comment,}/*** 在语法上,异常的定义方式和结构体是完全一样的。在发生问题时,可以抛出异常。*/exception InvalidOperation {1: i32 what,2: string why}/*** 啊哈,我们现在到了最Cool的环节,即定义服务。* (一个服务可以使用extends来继承另一个服务。)*/service Calculator extends shared.SharedService {/*** 服务中方法的定义非常类似于C语言的语法。它会包括一个返回值,* 一个参数列表以及一个可以抛出的异常列表(可选)* 可以提前告诉大家的是,定义参数列表的方法、定义异常列表的方法,* 和定义结构体的方法都是相似的,可以从下面的例子中看出。* 除了最后一个方法,其他的方法最后都要有一个逗号,大家可不要忽略这个细节。*/void ping(),i32 add(1:i32 num1, 2:i32 num2),/*** 在异常列表前,需要加throws关键字。*/i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),/*** 如下的这个方法有一个oneway修饰符* 表示这个方法在调用后会立即返回,不会等待远端的回复。* 要注意的是,oneway只能修饰void返回类型。* oneway在英语里就是“单向”的意思,还是很形象滴。*/oneway void zip()}/*** 在你使用thrift编译工具编译此文件后,* 会在当前目录产生一个“gen-<你选择的开发语言>”* 文件夹,比如你选择的是C++语言,则会产生gen-cpp文件夹,* 里面放着的便是thrift帮你生成好的代码,* 代码并不那么晦涩,你可以打开看一看。*/

Thrift的三个组件

Thrift 包含了三个主要的组件:protocol,transport 和 server。

  • protocol 定义了消息的序列化
  • transport 定义了消息是怎样在客户端和服务器端之间通信的
  • server 用于从 transport 接收序列化的消息,根据 protocol 反序列化之,调用用户定义的消息处理器,并序列化消息处理器的响应,然后再将它们写回 transport

protocol

Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本 (text) 和二进制 (binary) 传输协议。

为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数。常用协议有以下几种

TBinaryProtocol   二进制编码格式进行数据传输TCompactProtocol  高效率的,密集的二进制编码格式进行数据传输TJSONProtocol     使用 JSON 的数据编码协议进行数据传输TDebugProtocol    使用易懂的可读的文本格式,以便于debug

注意,客户端与服务端的通信协议要一致

transport

常见的几种transport

TBufferedTransport 有缓存,继承了TBufferBase,调用下一层TTransport 类进行读写操作TFramedTransport   有缓存,继承了TBufferBase, 以帧为传输单位TZlibTransport     调用下一层TTransport 类进行读写操作,不同的是对数据进行了压缩,对发生的消息压缩,对接收的消息先解压缩TMemoryBuffer      继承了TBufferBase,用于程序内部通信用,不涉及任何网络I/OTFileTransport     继承了TTransport,用于写数据到文件,两个线程,一个读,一个写TFDTransport       简单地写数据到文件和从文件读数据,它的write和read直接调用系统函数write,read

TTransport 是所有 Transport 类的父类,为上层提供了统一的接口而且通过 TTransport 即可访问各个子类不同实现

server

常见的几种erver

TSocket                阻塞型socket,用于客户端, 采用系统函数read和write进行读写数据TSSLSocket             继承了TSocket,阻塞型socket,用于客户端.采用 openssl 的接口进行读写数据TServerSocket          非阻塞型socket,用于服务器端TSimpleServer          单线程服务器端使用标准的阻塞式 I/OTThreadPoolServer      多线程服务器端使用标准的阻塞式 I/OTNonblockingServer     多线程服务器端使用非阻塞式 I/OTSSLServerSocket       用于服务端,非阻塞型socket    

Thrift工具的使用

thrift -gen ${开发语言} ${thrift接口描述文件}# 运行了上述命令之后,就会在当前文件夹下生成一个以“gen-${开发语言}”命名的文件夹,# 里面便是自动生成的代码。

Python Examlple

下载链接http://download.csdn.net/detail/lis_12/9866815

  • Example 1

    1. Thrift文件 Hello.thrift

      service HelloWorld {string ping(),string say(1:string msg)}
    2. 利用Thrift工具生成Python代码

      thrift -gen py Hello.thrift

      在生成的gen-py目录下新建server.py和client.py

      python还需要安装thrift,输入pip install thrift即可

      thrift在windows下和linux下生成的文件结构可能不同,注意下

    3. server.py

      #!/usr/bin/python# -*- coding: utf-8 -*-import socketimport syssys.path.append('./gen-py')from thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom thrift.server import TServerfrom Hello import HelloWorldfrom Hello.ttypes import *class HelloWorldHandler(object): """实现HellowWorld.Iface的接口""" def say(self, msg):     print "server receive say msg = %s"%msg     ret = "server apply " + msg     return ret def ping(self):     print "server receive ping"     return "server reply ping"def startServer(): """开启服务""" # 服务端处理实例 handler = HelloWorldHandler() # 关联处理器与HelloWorld服务的实现 processor = HelloWorld.Processor(handler) # 设置服务端端口 transport = TSocket.TServerSocket("localhost", 9090) # 传输层 tfactory = TTransport.TBufferedTransportFactory() # 数据传输协议 pfactory = TBinaryProtocol.TBinaryProtocolFactory() # 服务实例 server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) print "Starting thrift server in python..." server.serve() print "done!"if __name__ == "__main__": startServer()
    4. client.py

      #!/usr/bin/python# -*- coding: utf-8 -*-import syssys.path.append('./gen-py')from thrift import Thriftfrom thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom Hello import HelloWorldclass Client(HelloWorld.Client): """docstring for Client""" def __init__(self, protocol):     super(Client, self).__init__(protocol) def say(self, msg):     msg = "client %s"%msg     return super(Client, self).say(msg) def ping(self):     return super(Client, self).ping()def main(): try:     # 创建端口     transport = TSocket.TSocket('localhost', 9090)     # 传输层     transport = TTransport.TBufferedTransport(transport)     # 数据传输协议     protocol = TBinaryProtocol.TBinaryProtocol(transport)     # 客户端的实例     client = Client(protocol)     # 打开端口     transport.open()     print "client ping, server reply = " + client.ping()     print "client say, server reply = " + client.say("Hello!")     # 关闭端口     transport.close() except Thrift.TException, ex:     print "%s" % (ex.message)if __name__ == "__main__": main()

    command

    python server.py
    python client.py

    这里写图片描述

  • Example2

    返回当前时间,如果理解了Example1,事例2可忽略

    1. thrift文件,timepacket.thrift

      service requestTime {string curTime()}
    2. 将gen-py重命名为Time

    3. server.py

      #!/usr/bin/python# -*- coding: utf-8 -*-import socketimport sysimport timesys.path.append('./Time')from thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom thrift.server import TServerfrom timepacket import requestTimefrom timepacket.ttypes import *class handlerRequest(object): def curTime(self):     print "recv request"     return time.ctime()def startServer(): """开启服务""" # 服务端处理实例 handler = handlerRequest() # 关联处理器与HelloWorld服务的实现 processor = requestTime.Processor(handler) # 设置服务端端口 transport = TSocket.TServerSocket("localhost", 9090) # 传输层 tfactory = TTransport.TBufferedTransportFactory() # 数据传输协议 pfactory = TBinaryProtocol.TBinaryProtocolFactory() # 服务实例 server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) print "Starting thrift server in python..." server.serve() print "done!"if __name__ == "__main__": startServer()
    4. client.py

      #!/usr/bin/python# -*- coding: utf-8 -*-import syssys.path.append('./time')from thrift import Thriftfrom thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom timepacket import requestTimefrom timepacket.ttypes import *class Client(requestTime.Client): """docstring for Client""" def __init__(self, protocol):     super(Client, self).__init__(protocol) def curTime(self):     return super(Client, self).curTime()def main(): try:     # 创建端口     transport = TSocket.TSocket('localhost', 9090)     # 传输层     transport = TTransport.TBufferedTransport(transport)     # 数据传输协议     protocol = TBinaryProtocol.TBinaryProtocol(transport)     # 客户端的处理     client = Client(protocol)     # 打开端口     transport.open()     print "client requestTime, server reply = " + client.curTime()     # 关闭端口     transport.close() except Thrift.TException, ex:     print "%s" % (ex.message)if __name__ == "__main__": main()
原创粉丝点击