golang thrift 總結一下網絡上的一些坑

咱們以hello world來大概分析一下golang中的thrift包,而且扒一扒網絡上有關thrift的一些坑git

 

查看源碼,服務器定義以下:(詳見simple_server.go文件)golang

type TSimpleServer struct {
    quit    chan struct{}
    stopped int64

    processorFactory       TProcessorFactory   //實質是一個handler,用來相應客戶端的請求
    serverTransport        TServerTransport    //實質是一個socket
    inputTransportFactory  TTransportFactory   //實質是傳輸協議的具體操做類(詳細可見transport.go文件中TTransport結構體)
    outputTransportFactory TTransportFactory   //
    inputProtocolFactory   TProtocolFactory    //實質是傳輸協議(有compact、simplejson、json、binary四種協議,默認是binary)
tputProtocolFactory TProtocolFactory // }

 

在go語言中,建立一個thrift服務器有三種方法:(詳見simple_server.go文件)apache

func NewTSimpleServer2(processor TProcessor, serverTransport TServerTransport) *TSimpleServer {
    return NewTSimpleServerFactory2(NewTProcessorFactory(processor), serverTransport)
}

func NewTSimpleServer4(processor TProcessor, serverTransport TServerTransport, transportFactory TTransportFactory, protocolFactory TProtocolFactory) *TSimpleServer {
    return NewTSimpleServerFactory4(NewTProcessorFactory(processor),
        serverTransport,
        transportFactory,
        protocolFactory,
    )
}

func NewTSimpleServer6(processor TProcessor, serverTransport TServerTransport, inputTransportFactory TTransportFactory, outputTransportFactory TTransportFactory, inputProtocolFactory TProtocolFactory, outputProtocolFactory TProtocolFactory) *TSimpleServer {
    return NewTSimpleServerFactory6(NewTProcessorFactory(processor),
        serverTransport,
        inputTransportFactory,
        outputTransportFactory,
        inputProtocolFactory,
        outputProtocolFactory,
    )
}

這三個函數分別調用了工廠函數json

NewTSimpleServerFactory2;
NewTSimpleServerFactory4;
NewTSimpleServerFactory6;
func NewTSimpleServerFactory2(processorFactory TProcessorFactory, serverTransport TServerTransport) *TSimpleServer {
    return NewTSimpleServerFactory6(processorFactory,
        serverTransport,
        NewTTransportFactory(),
        NewTTransportFactory(),
        NewTBinaryProtocolFactoryDefault(),
        NewTBinaryProtocolFactoryDefault(),
    )
}

func NewTSimpleServerFactory4(processorFactory TProcessorFactory, serverTransport TServerTransport, transportFactory TTransportFactory, protocolFactory TProtocolFactory) *TSimpleServer {
    return NewTSimpleServerFactory6(processorFactory,
        serverTransport,
        transportFactory,
        transportFactory,
        protocolFactory,
        protocolFactory,
    )
}

func NewTSimpleServerFactory6(processorFactory TProcessorFactory, serverTransport TServerTransport, inputTransportFactory TTransportFactory, outputTransportFactory TTransportFactory, inputProtocolFactory TProtocolFactory, outputProtocolFactory TProtocolFactory) *TSimpleServer {
    return &TSimpleServer{
        processorFactory:       processorFactory,
        serverTransport:        serverTransport,
        inputTransportFactory:  inputTransportFactory,
        outputTransportFactory: outputTransportFactory,
        inputProtocolFactory:   inputProtocolFactory,
        outputProtocolFactory:  outputProtocolFactory,
        quit: make(chan struct{}, 1),
    }
}

好啦!如今假如咱們須要建立一個以二進制協議傳輸的thrift服務器,那麼能夠用以下代碼簡單實現:服務器

    serverTransport, err := thrift.NewTServerSocket("127.0.0.1:8808")
    if err != nil {
        fmt.Println("Error!", err)
        return
    }
    handler := &rpcService{}
    processor := rpc.NewRpcServiceProcessor(handler)
    server := thrift.NewTSimpleServer2(processor, serverTransport)
    fmt.Println("thrift server in localhost")

    server.Serve()

另外我在網上查看這方面資料的時候,發現你們都用的NewTSimpleServer4這個函數,而後本身又建立一遍NewTTransportFactory以及NewTBinaryProtocolFactoryDefault。網絡

如今咱們分析一下源碼,發現此舉實乃畫蛇添足。這是第一坑。socket

 

接下來講說如何用golang thrift編寫客戶端,查看網絡上的一些寫法,發現根本用不了,服務器會阻塞住!仍是從源碼來分析:ide

在thrift自動生成的代碼中,會生成一個關於客戶端的示例。函數

// Autogenerated by Thrift Compiler (0.9.3)
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING

package main

import (
    "flag"
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "math"
    "net"
    "net/url"
    "os"
    "strconv"
    "strings"
    "vic/rpc"
)

func Usage() {
    fmt.Fprintln(os.Stderr, "Usage of ", os.Args[0], " [-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:")
    flag.PrintDefaults()
    fmt.Fprintln(os.Stderr, "\nFunctions:")
    fmt.Fprintln(os.Stderr, "  Video request(string vid, string cid, string platform, string url, string clientVersion)")
    fmt.Fprintln(os.Stderr)
    os.Exit(0)
}

func main() {
    flag.Usage = Usage
    var host string
    var port int
    var protocol string
    var urlString string
    var framed bool
    var useHttp bool
    var parsedUrl url.URL
    var trans thrift.TTransport
    _ = strconv.Atoi
    _ = math.Abs
    flag.Usage = Usage
    flag.StringVar(&host, "h", "localhost", "Specify host and port")
    flag.IntVar(&port, "p", 9090, "Specify port")
    flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)")
    flag.StringVar(&urlString, "u", "", "Specify the url")
    flag.BoolVar(&framed, "framed", false, "Use framed transport")
    flag.BoolVar(&useHttp, "http", false, "Use http")
    flag.Parse()

    if len(urlString) > 0 {
        parsedUrl, err := url.Parse(urlString)
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
            flag.Usage()
        }
        host = parsedUrl.Host
        useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http"
    } else if useHttp {
        _, err := url.Parse(fmt.Sprint("http://", host, ":", port))
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
            flag.Usage()
        }
    }

    cmd := flag.Arg(0)
    var err error
    if useHttp {
        trans, err = thrift.NewTHttpClient(parsedUrl.String())
    } else {
        portStr := fmt.Sprint(port)
        if strings.Contains(host, ":") {
            host, portStr, err = net.SplitHostPort(host)
            if err != nil {
                fmt.Fprintln(os.Stderr, "error with host:", err)
                os.Exit(1)
            }
        }
        trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))
        if err != nil {
            fmt.Fprintln(os.Stderr, "error resolving address:", err)
            os.Exit(1)
        }
        if framed {
            trans = thrift.NewTFramedTransport(trans)
        }
    }
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error creating transport", err)
        os.Exit(1)
    }
    defer trans.Close()
    var protocolFactory thrift.TProtocolFactory
    switch protocol {
    case "compact":
        protocolFactory = thrift.NewTCompactProtocolFactory()
        break
    case "simplejson":
        protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
        break
    case "json":
        protocolFactory = thrift.NewTJSONProtocolFactory()
        break
    case "binary", "":
        protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
        break
    default:
        fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol)
        Usage()
        os.Exit(1)
    }
    client := rpc.NewVideoServiceClientFactory(trans, protocolFactory)
    if err := trans.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err)
        os.Exit(1)
    }

    switch cmd {
    case "request":
        if flag.NArg()-1 != 5 {
            fmt.Fprintln(os.Stderr, "Request requires 5 args")
            flag.Usage()
        }
        argvalue0 := flag.Arg(1)
        value0 := argvalue0
        argvalue1 := flag.Arg(2)
        value1 := argvalue1
        argvalue2 := flag.Arg(3)
        value2 := argvalue2
        argvalue3 := flag.Arg(4)
        value3 := argvalue3
        argvalue4 := flag.Arg(5)
        value4 := argvalue4
        fmt.Print(client.Request(value0, value1, value2, value3, value4))
        fmt.Print("\n")
        break
    case "":
        Usage()
        break
    default:
        fmt.Fprintln(os.Stderr, "Invalid function ", cmd)
    }
}
View Code

咱們一部分一部分來分析分析:ui

flag.Usage = Usage
    var host string
    var port int
    var protocol string
    var urlString string
    var framed bool
    var useHttp bool
    var parsedUrl url.URL
    var trans thrift.TTransport
    _ = strconv.Atoi
    _ = math.Abs
    flag.Usage = Usage
    flag.StringVar(&host, "h", "localhost", "Specify host and port")
    flag.IntVar(&port, "p", 9090, "Specify port")
    flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)")
    flag.StringVar(&urlString, "u", "", "Specify the url")
    flag.BoolVar(&framed, "framed", false, "Use framed transport")
    flag.BoolVar(&useHttp, "http", false, "Use http")
    flag.Parse()

這些代碼是設置了一些程序的啓動命令,例如默認地址是loacalhost,咱們能夠根據client.exe -h xxx.xxx.xxx.xxx之類的命令來修改

咱們發現這些代碼都不是咱們須要的,pass,繼續看

if len(urlString) > 0 {
        parsedUrl, err := url.Parse(urlString)
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
            flag.Usage()
        }
        host = parsedUrl.Host
        useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http"
    } else if useHttp {
        _, err := url.Parse(fmt.Sprint("http://", host, ":", port))
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
            flag.Usage()
        }
    }

    cmd := flag.Arg(0)
    var err error
    if useHttp {
        trans, err = thrift.NewTHttpClient(parsedUrl.String())
    } else {
        portStr := fmt.Sprint(port)
        if strings.Contains(host, ":") {
            host, portStr, err = net.SplitHostPort(host)
            if err != nil {
                fmt.Fprintln(os.Stderr, "error with host:", err)
                os.Exit(1)
            }
        }
        trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))
        if err != nil {
            fmt.Fprintln(os.Stderr, "error resolving address:", err)
            os.Exit(1)
        }
        if framed {
            trans = thrift.NewTFramedTransport(trans)
        }
    }

 

這部分主要做用是解析url參數,從中取得host以及port。而且用於生成一個TTransport,上面紅線加粗的函數定義在源碼中以下:

func NewTHttpClient(urlstr string) (TTransport, error) {
    return NewTHttpClientWithOptions(urlstr, THttpClientOptions{})
}

func NewTSocket(hostPort string) (*TSocket, error) {
    return NewTSocketTimeout(hostPort, 0)
}

細心的朋友們可能發現了端倪,第二個函數的返回值是一個TSocket指針,並非TTransport,是否是有啥問題?不急,咱們看看這兩個結構體的定義就知道了:

    type TTransport interface {
    io.ReadWriteCloser
    Flusher
    ReadSizeProvider

    // Opens the transport for communication
    Open() error

    // Returns true if the transport is open
    IsOpen() bool
}

原來TTransport是一個接口類型,而TSocket則實現了該接口!

目前爲止,咱們得到了建立客戶端所須要的關鍵代碼:

trans, err = thrift.NewTHttpClient(parsedUrl.String())

trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))

OK,繼續分析示例!

    var protocolFactory thrift.TProtocolFactory
    switch protocol {
    case "compact":
        protocolFactory = thrift.NewTCompactProtocolFactory()
        break
    case "simplejson":
        protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
        break
    case "json":
        protocolFactory = thrift.NewTJSONProtocolFactory()
        break
    case "binary", "":
        protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
        break
    default:
        fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol)
        Usage()
        os.Exit(1)
    }
    client := rpc.NewVideoServiceClientFactory(trans, protocolFactory) if err := trans.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err)
        os.Exit(1)
    }

switch語句是根據咱們所輸入的參數,選擇傳輸協議。最後經過NewVideoServiceClientFactory函數 完成客戶端的建立

最後,總結一下,假如咱們要建立一個以二進制爲傳輸協議,那麼咱們能夠編寫以下代碼來完成:

    transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "8808"))
    if err != nil {
        fmt.Fprintln(os.Stderr, "error resolving address:", err)
        os.Exit(1)
    }
    
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()

    client := NewRpcServiceClientFactory(transport, protocolFactory)
    if err := transport.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:8808", " ", err)
        os.Exit(1)
    }
    defer transport.Close()
    res, _ := client.SayHi(「World」)
相關文章
相關標籤/搜索