这个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示例.