咱們以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) } }
咱們一部分一部分來分析分析: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」)