這個thrift的簡單示例來自於官網 (http://thrift.apache.org/tutorial/go), 因為官方提供的例子簡單易懂, 所以沒有必要額外考慮新的例子. 關於安裝的教程, 可以參考https://www.cnblogs.com/albizzia/p/10838646.html, 關於thrift文件的語法, 可以參考: https://www.cnblogs.com/albizzia/p/10838646.html.
運行下面的示例, 除了需要安裝thrift外, 還有一些要求:
(1) go需要版本達到1.7或者更高.
(2) GOPATH可能需要調整, 或者人工將go thrift的庫文件放置到合適的位置.
(3) thrift庫和編譯器需要是相同的版本. 如果版本不一致, 程序可能也可以運行, 但是這個不是官方保證的. 為了使用一個特定版本的庫, 要么克隆那個版本的倉庫, 要么使用dep(https://golang.github.io/dep/)或者go modules(https://github.com/golang/go/wiki/Modules)一類的包管理器.
(4) 需要調用如下命令:
go get github.com/apache/thrift/lib/go/thrift
首先給出shared.thrift文件的定義:
/**
* 這個Thrift文件包含一些共享定義
*/
namespace go shared
struct SharedStruct {
1: i32 key
2: string value
}
service SharedService {
SharedStruct getStruct(1: i32 key)
}
然后給出tutorial.thrift的定義:
/**
* Thrift引用其他thrift文件, 這些文件可以從當前目錄中找到, 或者使用-I的編譯器參數指示.
* 引入的thrift文件中的對象, 使用被引入thrift文件的名字作為前綴, 例如shared.SharedStruct.
*/
include "shared.thrift"
namespace go tutorial
// 定義別名
typedef i32 MyInteger
/**
* 定義常量. 復雜的類型和結構體使用JSON表示法.
*/
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
/**
* 枚舉是32位數字, 如果沒有顯式指定值,從1開始.
*/
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
/**
* 結構體由一組成員來組成, 一個成員包括數字標識符, 類型, 符號名, 和一個可選的默認值.
* 成員可以加"optional"修飾符, 用來表明如果這個值沒有被設置, 那么他們不會被串行化到
* 結果中. 不過這個在有些語言中, 需要顯式控制.
*/
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
// 結構體也可以作為異常
exception InvalidOperation {
1: i32 whatOp,
2: string why
}
/**
* 服務需要一個服務名, 加上一個可選的繼承, 使用extends關鍵字
*/
service Calculator extends shared.SharedService {
/**
* 方法定義和C語言一樣, 有返回值, 參數或者一些它可能拋出的異常, 參數列表和異常列表的
* 寫法與結構體中的成員列表定義一致.
*/
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* 這個方法有oneway修飾符, 表示客戶段發送一個請求, 然后不會等待回應. Oneway方法
* 的返回值必須是void
*/
oneway void zip()
}
將上述文件放置在同一個文件夾, 然后編譯上述的thrift文件:
$ thrift -r --gen go tutorial.thrift
然后在gen-go的子文件夾中, 可以看到編譯生成的go文件. 下面, 我們來分析一下生成的go文件, 這里, 我們只分析調用如下命令生成的go文件:
$ thrift --gen go shared.thrift
調用上述命令, 在gen-go子文件夾中會有個文件夾叫做shared, 這個文件夾對應go中的包名, shared文件夾中有shared-consts.go, shared.go, GoUnusedProtection__.go, 以及一個shared_service-remote文件夾.
關於GoUnusedProtection__.go文件, 具體用處不太清楚.
關於shared-consts.go文件, 這個文件用來定義thrift中的常量.
關於shared.go文件, 我們從上到下, 簡單地看一下:
(1) SharedStruct結構體, 這個結構體對應於thrift中的SharedStruct, 這個結構體中的成員都是大寫開頭的, 表示可以額直接訪問, 還有以下函數:
1) 生成結構體的函數, NewSharedStruct
2) 獲取結構體中成員的函數, 包括 GetKey和GetValue
3) 從Protocol中讀取數據, 設置自身值的Read函數
4) 從Protocol中讀取數據, 設置第N個字段的ReadField1和ReadField2函數
5) 將自身值寫入到Protocol中的Write函數
6) 將自身第N個字段寫入到Protocol中的writeField1和writeField2函數 (這兩個函數在包外不可見)
7) 返回結構體的字符串表示的String函數
(2) thrift文件中SharedService服務對應的接口:
type SharedService interface {
// Parameters:
// - Key
GetStruct(ctx context.Context, key int32) (r *SharedStruct, err error)
}
(3) SharedServiceClient結構體, 及其相關函數
type SharedServiceClient struct {
c thrift.TClient
}
1) 構造函數, 包括如下方式:
func NewSharedServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *SharedServiceClient func NewSharedServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *SharedServiceClient func NewSharedServiceClient(c thrift.TClient) *SharedServiceClient
2) SharedServiceClient實現SharedService接口的函數:
func (p *SharedServiceClient) GetStruct(ctx context.Context, key int32) (r *SharedStruct, err error) {
var _args0 SharedServiceGetStructArgs
_args0.Key = key
var _result1 SharedServiceGetStructResult
if err = p.Client_().Call(ctx, "getStruct", &_args0, &_result1); err != nil {
return
}
return _result1.GetSuccess(), nil
}
(3) SharedServiceProcessor結構體, 及其函數:
type SharedServiceProcessor struct {
processorMap map[string]thrift.TProcessorFunction
handler SharedService
}
// A processor is a generic object which operates upon an input stream and
// writes to some output stream.
type TProcessor interface {
Process(ctx context.Context, in, out TProtocol) (bool, TException)
}
1) 構造函數
func NewSharedServiceProcessor(handler SharedService) *SharedServiceProcessor {
self2 := &SharedServiceProcessor{handler:handler, processorMap:make(map[string]thrift.TProcessorFunction)}
self2.processorMap["getStruct"] = &sharedServiceProcessorGetStruct{handler:handler}
return self2
}
2)操作處理函數(processorMap)的方法
func (p *SharedServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) func (p *SharedServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) func (p *SharedServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction
3) 事件循環處理函數
func (p *SharedServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
name, _, seqId, err := iprot.ReadMessageBegin()
if err != nil { return false, err }
if processor, ok := p.GetProcessorFunction(name); ok {
return processor.Process(ctx, seqId, iprot, oprot)
}
iprot.Skip(thrift.STRUCT)
iprot.ReadMessageEnd()
x3 := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function " + name)
oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId)
x3.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush(ctx)
return false, x3
}
(4) sharedServiceProcessorGetStruct 用來實現SharedService的GetStruct函數, 這個結構體需要實現TProcessorFunction接口, 所以需要實現TProcessorFunction的Process函數.
type sharedServiceProcessorGetStruct struct {
handler SharedService
}
type TProcessorFunction interface {
Process(ctx context.Context, seqId int32, in, out TProtocol) (bool, TException)
}
sharedServiceProcessorGetStruct需要實現TProcessorFunction接口中的Process函數.
(5) SharedServiceGetStructArgs是傳入thrift中GetStruct函數的參數, 實現細節類似於上面的SharedStruct結構體.
(6) SharedServiceGetStructResult是thrift中GetStruct函數的結果, 實現細節類似於上面的SharedStruct結構體.
關於shared_service-remote中的文件, 是shared.go文件中結構體和函數的使用示例, 可以參考這個go文件寫出使用thrift的程序.
服務端服務結構體
import (
"context"
"fmt"
"strconv"
"shared"
"tutorial"
)
type CalculatorHandler struct {
log map[int]*shared.SharedStruct
}
func NewCalculatorHandler() *CalculatorHandler {
return &CalculatorHandler{log: make(map[int]*shared.SharedStruct)}
}
func (p *CalculatorHandler) Ping(ctx context.Context) (err error) {
fmt.Print("ping()\n")
return nil
}
func (p *CalculatorHandler) Add(ctx context.Context, num1 int32, num2 int32) (retval17 int32, err error) {
fmt.Print("add(", num1, ",", num2, ")\n")
return num1 + num2, nil
}
func (p *CalculatorHandler) Calculate(ctx context.Context, 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(ctx context.Context, key int32) (*shared.SharedStruct, error) {
fmt.Print("getStruct(", key, ")\n")
v, _ := p.log[int(key)]
return v, nil
}
func (p *CalculatorHandler) Zip(ctx context.Context) (err error) {
fmt.Print("zip()\n")
return nil
}
服務端代碼
import (
"crypto/tls"
"fmt"
"github.com/apache/thrift/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()
}
客戶端代碼
import (
"context"
"crypto/tls"
"fmt"
"tutorial"
"github.com/apache/thrift/lib/go/thrift"
)
var defaultCtx = context.Background()
func handleClient(client *tutorial.CalculatorClient) (err error) {
client.Ping(defaultCtx)
fmt.Println("ping()")
sum, _ := client.Add(defaultCtx, 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(defaultCtx, 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(defaultCtx, 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(defaultCtx, 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, err = transportFactory.GetTransport(transport)
if err != nil {
return err
}
defer transport.Close()
if err := transport.Open(); err != nil {
return err
}
iprot := protocolFactory.GetProtocol(transport)
oprot := protocolFactory.GetProtocol(transport)
return handleClient(tutorial.NewCalculatorClient(thrift.NewTStandardClient(iprot, oprot)))
}
生成應用代碼
import (
"flag"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"os"
)
func Usage() {
fmt.Fprint(os.Stderr, "Usage of ", os.Args[0], ":\n")
flag.PrintDefaults()
fmt.Fprint(os.Stderr, "\n")
}
func main() {
flag.Usage = Usage
server := flag.Bool("server", false, "Run server")
protocol := flag.String("P", "binary", "Specify the protocol (binary, compact, json, simplejson)")
framed := flag.Bool("framed", false, "Use framed transport")
buffered := flag.Bool("buffered", false, "Use buffered transport")
addr := flag.String("addr", "localhost:9090", "Address to listen to")
secure := flag.Bool("secure", false, "Use tls secure transport")
flag.Parse()
var protocolFactory thrift.TProtocolFactory
switch *protocol {
case "compact":
protocolFactory = thrift.NewTCompactProtocolFactory()
case "simplejson":
protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
case "json":
protocolFactory = thrift.NewTJSONProtocolFactory()
case "binary", "":
protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
default:
fmt.Fprint(os.Stderr, "Invalid protocol specified", protocol, "\n")
Usage()
os.Exit(1)
}
var transportFactory thrift.TTransportFactory
if *buffered {
transportFactory = thrift.NewTBufferedTransportFactory(8192)
} else {
transportFactory = thrift.NewTTransportFactory()
}
if *framed {
transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
}
if *server {
if err := runServer(transportFactory, protocolFactory, *addr, *secure); err != nil {
fmt.Println("error running server:", err)
}
} else {
if err := runClient(transportFactory, protocolFactory, *addr, *secure); err != nil {
fmt.Println("error running client:", err)
}
}
}
啟動程序命令
$./thrift -server
$ ./thrift
以上就是使用go語言的thrift示例.
