zoukankan      html  css  js  c++  java
  • golang thrift 总结一下网络上的一些坑

    我们以hello world来大概分析一下golang中的thrift包,并且扒一扒网络上有关thrift的一些坑

    查看源码,服务器定义如下:(详见simple_server.go文件)

    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文件)

    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,
        )
    }

    这三个函数分别调用了工厂函数

    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。

    现在我们分析一下源码,发现此举实乃多此一举。这是第一坑。

    接下来说说如何用golang thrift编写客户端,查看网络上的一些写法,发现根本用不了,服务器会阻塞住!还是从源码来分析:

    在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, "
    Functions:")
        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("
    ")
            break
        case "":
            Usage()
            break
        default:
            fmt.Fprintln(os.Stderr, "Invalid function ", cmd)
        }
    }
    View Code

    我们一部分一部分来分析分析:

    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”)
  • 相关阅读:
    强大的js时间选择器 万年历
    js 锚点平滑定位
    php str_replace的替换漏洞
    绝对路径 相对路径 小结
    昨天去了长城
    [转载]71个做饭技巧好好记住了,不要忘记给自己做一顿美餐噢
    最近心情很糟,情绪很低落
    用javascript实现html页面之间的参数传递的四种方法
    解决ajax缓存问题
    [转载]30岁前男人需要完成的事
  • 原文地址:https://www.cnblogs.com/ka200812/p/5865213.html
Copyright © 2011-2022 走看看