google thrift使用指南
本文分析http://thrift.apache.org/tutorial/go 上提供的tutorial。编程语言采用go。
1.编写thrift脚本
tutorial example提供两个thrift脚本:tutorial.thrift 和shared.thrift
去掉其中的注释,内容如下:
- shared.thrift
namespace cpp shared
namespace d share // "shared" would collide with the eponymous D keyword.
namespace dart shared
namespace java shared
namespace perl shared
namespace php shared
namespace haxe shared
namespace netcore shared
struct SharedStruct {
1: i32 key
2: string value
}
service SharedService {
SharedStruct getStruct(1: i32 key)
}
- tutorial.thrift
include "shared.thrift"
namespace cpp tutorial
namespace d tutorial
namespace dart tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial
namespace haxe tutorial
namespace netcore tutorial
typedef i32 MyInteger
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
exception InvalidOperation {
1: i32 whatOp,
2: string why
}
service Calculator extends shared.SharedService {
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
oneway void zip()
}
分析:
以上两个.thrift文件中定义了结构体和service。service中定义的函数就是你的应用需要的接口。service Calculator是对shared.SharedService的继承。(service Calculator extends shared.SharedService )
2.使用thrift.exe(windows平台下)生成对应语言的代码
这里以go语言为例:
thrift -r --gen go tutorial.thrift
运行以上命令后,将在.thrift文件所在目录下生成gen-go文件夹,该文件夹下会有shared和tutorial两个文件夹,对应shared和tutorial两个package。
SharedServiceClient 类型实现了GetStruct接口,编写客户端代码时,我们的工作就是利用这个类型+接口的组合来实现对远端函数的调用。
tutorial文件夹目录如下:
tutorial-consts.go中是.thrift中的变量、常量、结构体等的声明。较为简单,不做阐述。重点分析tutorial.go文件。
tutorial.go中主要包含以下三个内容:
1).接口声明
type Calculator interface {
shared.SharedService
Ping() (err error)
// Parameters:
// - Num1
// - Num2
Add(num1 int32, num2 int32) (r int32, err error)
// Parameters:
// - Logid
// - W
Calculate(logid int32, w *Work) (r int32, err error)
Zip() (err error)
}
2)接口实现
type CalculatorClient struct {
*shared.SharedServiceClient
}
func (p *CalculatorClient) Ping() (err error) {
if err = p.sendPing(); err != nil { return }
return p.recvPing()
}
func (p *CalculatorClient) Add(num1 int32, num2 int32) (r int32, err error) {
if err = p.sendAdd(num1, num2); err != nil { return }
return p.recvAdd()
}
func (p *CalculatorClient) Calculate(logid int32, w *Work) (r int32, err error) {
if err = p.sendCalculate(logid, w); err != nil { return }
return p.recvCalculate()
}
func (p *CalculatorClient) Zip() (err error) {
if err = p.sendZip(); err != nil { return }
return
}
3)提供创建CalculatorClient的接口
func NewCalculatorClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *CalculatorClient {
return &CalculatorClient{SharedServiceClient: shared.NewSharedServiceClientFactory(t, f)}}
shared文件夹下的目录与tutorial相似,相关文件所做的事情也相同,这里不做重复叙述。
3.客户端代码
import (
"crypto/tls"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"tutorial"
)
func handleClient(client *tutorial.CalculatorClient) (err error) {
client.Ping()
fmt.Println("ping()")
sum, _ := client.Add(1, 1)
fmt.Print("1+1=", sum, "\n")
work := tutorial.NewWork()
work.Op = tutorial.Operation_DIVIDE
work.Num1 = 1
work.Num2 = 0
quotient, err := client.Calculate(1, work)
if err != nil {
switch v := err.(type) {
case *tutorial.InvalidOperation:
fmt.Println("Invalid operation:", v)
default:
fmt.Println("Error during operation:", err)
}
return err
} else {
fmt.Println("Whoa we can divide by 0 with new value:", quotient)
}
work.Op = tutorial.Operation_SUBTRACT
work.Num1 = 15
work.Num2 = 10
diff, err := client.Calculate(1, work)
if err != nil {
switch v := err.(type) {
case *tutorial.InvalidOperation:
fmt.Println("Invalid operation:", v)
default:
fmt.Println("Error during operation:", err)
}
return err
} else {
fmt.Print("15-10=", diff, "\n")
}
log, err := client.GetStruct(1)
if err != nil {
fmt.Println("Unable to get struct:", err)
return err
} else {
fmt.Println("Check log:", log.Value)
}
return err
}
func runClient(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string, secure bool) error {
var transport thrift.TTransport
var err error
if secure {
cfg := new(tls.Config)
cfg.InsecureSkipVerify = true
transport, err = thrift.NewTSSLSocket(addr, cfg)
} else {
transport, err = thrift.NewTSocket(addr)
}
if err != nil {
fmt.Println("Error opening socket:", err)
return err
}
transport = transportFactory.GetTransport(transport)
defer transport.Close()
if err := transport.Open(); err != nil {
return err
}
return handleClient(tutorial.NewCalculatorClientFactory(transport, protocolFactory))
}
客户端代码书写主要包含两步骤:创建CalculatorClient和调用CalculatorClient实现的接口。当通过CalculatorClient调用实现的接口时,thrift实现在server端调用相同的接口。
4.server端代码
server handler代码
import (
"fmt"
"shared"
"strconv"
"tutorial"
)
type CalculatorHandler struct {
log map[int]*shared.SharedStruct
}
func NewCalculatorHandler() *CalculatorHandler {
return &CalculatorHandler{log: make(map[int]*shared.SharedStruct)}
}
func (p *CalculatorHandler) Ping() (err error) {
fmt.Print("ping()\n")
return nil
}
func (p *CalculatorHandler) Add(num1 int32, num2 int32) (retval17 int32, err error) {
fmt.Print("add(", num1, ",", num2, ")\n")
return num1 + num2, nil
}
func (p *CalculatorHandler) Calculate(logid int32, w *tutorial.Work) (val int32, err error) {
fmt.Print("calculate(", logid, ", {", w.Op, ",", w.Num1, ",", w.Num2, "})\n")
switch w.Op {
case tutorial.Operation_ADD:
val = w.Num1 + w.Num2
break
case tutorial.Operation_SUBTRACT:
val = w.Num1 - w.Num2
break
case tutorial.Operation_MULTIPLY:
val = w.Num1 * w.Num2
break
case tutorial.Operation_DIVIDE:
if w.Num2 == 0 {
ouch := tutorial.NewInvalidOperation()
ouch.WhatOp = int32(w.Op)
ouch.Why = "Cannot divide by 0"
err = ouch
return
}
val = w.Num1 / w.Num2
break
default:
ouch := tutorial.NewInvalidOperation()
ouch.WhatOp = int32(w.Op)
ouch.Why = "Unknown operation"
err = ouch
return
}
entry := shared.NewSharedStruct()
entry.Key = logid
entry.Value = strconv.Itoa(int(val))
k := int(logid)
/*
oldvalue, exists := p.log[k]
if exists {
fmt.Print("Replacing ", oldvalue, " with ", entry, " for key ", k, "\n")
} else {
fmt.Print("Adding ", entry, " for key ", k, "\n")
}
*/
p.log[k] = entry
return val, err
}
func (p *CalculatorHandler) GetStruct(key int32) (*shared.SharedStruct, error) {
fmt.Print("getStruct(", key, ")\n")
v, _ := p.log[int(key)]
return v, nil
}
func (p *CalculatorHandler) Zip() (err error) {
fmt.Print("zip()\n")
return nil
}
CalculatorHandler类型实现了.thrift中定义的接口,这个实现是由程序员自己实现的,这是server端实际要实现的功能。不同于上面的tutorial.go中的接口实现(由工具自动生成)。
server代码
import (
"crypto/tls"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"tutorial"
)
func runServer(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string, secure bool) error {
var transport thrift.TServerTransport
var err error
if secure {
cfg := new(tls.Config)
if cert, err := tls.LoadX509KeyPair("server.crt", "server.key"); err == nil {
cfg.Certificates = append(cfg.Certificates, cert)
} else {
return err
}
transport, err = thrift.NewTSSLServerSocket(addr, cfg)
} else {
transport, err = thrift.NewTServerSocket(addr)
}
if err != nil {
return err
}
fmt.Printf("%T\n", transport)
handler := NewCalculatorHandler()
processor := tutorial.NewCalculatorProcessor(handler)
server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)
fmt.Println("Starting the simple server... on ", addr)
return server.Serve()
}
server端代码书写也主要包含两步:1)定义一个类型handler实现.thrift定义的接口;2)启动server(server.Serve())。
5.综述
client端主要是利用thrift.exe工具生成的代码,不需要修改,只需要完成调用即可。server端主要是定义一个server,并实现.thrift文件中定义的接口,最后启动server即可。当client实例调用一个接口函数时候,thrift机制会实现server实例相同函数的调用。
thrift机制实现可能很复杂,但是使用非常简单,只需要实现一个handler即可。