google thrift

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即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值