thrift简单示例 (go语言)

Posted albizzia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了thrift简单示例 (go语言)相关的知识,希望对你有一定的参考价值。

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

 

以上是关于thrift简单示例 (go语言)的主要内容,如果未能解决你的问题,请参考以下文章

thrift在C++中的应用

[go微服务-17] gRPC和 Apache Thrift 之间 如何进行选型?

go语言使用thrift协议实现客户端和服务端报not enough arguments in call to oprot.WriteMessageBegin错误解决方案

go语言使用thrift协议实现客户端和服务端报not enough arguments in call to oprot.WriteMessageBegin错误解决方案

go语言使用thrift协议实现客户端和服务端报not enough arguments in call to oprot.WriteMessageBegin错误解决方案

go语言使用thrift协议实现客户端和服务端报not enough arguments in call to oprot.WriteMessageBegin错误解决方案