golang thrift 源碼分析,伺服器和用戶端究竟是如何工作的

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

首先編寫thrift檔案(rpcserver.thrift),運行thrift --gen go rpcserver.thrift,產生代碼

namespace go rpcservice RpcService {    string SayHi(1: string name);    void SayHello(1: string name);}

搭建一個以二進位為傳輸協議的伺服器如下:

type rpcService struct{}func (this *rpcService)SayHi(name string)(r string, err error){    fmt.Println("Hi ", name)    r = "Hello "+name    err = nil    return}func (this *rpcService)SayHello(name string)err error{    fmt.Println("Hello ", name)    err = nil    return}func StartServer(){    serverTransport, err := thrift.NewTServerSocket("127.0.0.1:8808")    if err != nil {        fmt.Println("Error!", err)        return    }    handler := &rpcService{}    processor := NewRpcServiceProcessor(handler)    server := thrift.NewTSimpleServer2(processor, serverTransport)    fmt.Println("thrift server in localhost")    server.Serve()}

查看自動產生的程式碼recserver.go,我們發現 NewRpcServiceProcessor函數代碼如下:

func NewRpcServiceProcessor(handler RpcService) *RpcServiceProcessor {    self4 := &RpcServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)}    self4.processorMap["SayHi"] = &rpcServiceProcessorSayHi{handler: handler}    self4.processorMap["SayHello"] = &rpcServiceProcessorSayHello{handler: handler}    return self4}

也就是說,thrift通過key-value儲存了我們實際將要啟動並執行函數,最終通過handler來執行。

這裡就有點像我們使用golang系統中的http包中的ListenAndServer()函數時,提前通過Handfunc來設定好函數路由是一個意思。

再看看Serve()函數是如何?的:

func (p *TSimpleServer) Serve() error {    err := p.Listen()    if err != nil {        return err    }    p.AcceptLoop()    return nil}func (p *TSimpleServer) AcceptLoop() error {    for {        client, err := p.serverTransport.Accept()
if err != nil{
//.......被我刪掉
} if client != nil { go func() { if err := p.processRequests(client); err != nil { log.Println("error processing request:", err) } }() } }}

Serve()函數負責監聽串連到伺服器上的client,並且通過processRequests()函數來處理client。實際處理過程中,伺服器會擷取client的processor,然後進一步處理client的請求。這部分先暫停一下,我們來分析一下client端的工作原理,之後再回過頭來看看會比較清晰一些

 

首先我們架設client端如下,並且通過client端來發送一個SayHi的操作:   

    transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "8808"))
if err != nil { //... } protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() client := NewRpcServiceClientFactory(transport, protocolFactory) if err := transport.Open(); err != nil { //... } defer transport.Close()
res, _ := client.SayHi("wordl")

現在問題來了,這個SayHi是如何通知給伺服器的呢?不急,看源碼

在我們調用thrift --gen go XXX命令的時候,thrift已經給我們產生了SayHi過程的代碼,如下:

func (p *RpcServiceClient) SayHi(name string) (r string, err error) {    if err = p.sendSayHi(name); err != nil {        return    }    return p.recvSayHi()}

其中RpcServiceClient類型就是我們的client,可以看到先調用了一個sendSayHi,如果沒有錯誤的話,又調用了一個recvSayHi。

其實sendSayHi就是我們通知伺服器執行SayHi()函數的關鍵,而recvSayHi是接受伺服器的執行結果的。

一起看下sendSayHi是如何?的(代碼被我精簡,這保留了關鍵區段,完整代碼可以自己通過thrift命令產生查看)

func (p *RpcServiceClient) sendSayHi(name string) (err error) {    oprot := p.OutputProtocol  //擷取傳輸協議        if err = oprot.WriteMessageBegin("SayHi", thrift.CALL, p.SeqId); err != nil { //發送SayHi字元創,告訴伺服器將來執行的函數        return    }    args := RpcServiceSayHiArgs{ //構建參數        Name: name,    }    if err = args.Write(oprot); err != nil {  //將參數發送給伺服器        return    }    if err = oprot.WriteMessageEnd(); err != nil { //通知伺服器發送完畢        return    }    return oprot.Flush()}

通過這樣的一系列資料轉送,伺服器通過路由解析,便可以正確的知道該執行哪個函數了。thrift的精髓也正在此,實現了rpc架構,用戶端只需要簡單的調用client.SayHi(),不必知道這是本地調用還是遠程調用。

 

好了,既然請求發出了,我們現在當然看看伺服器是如何響應的,在源碼中,有一個函數是專門響應用戶端請求的:

func (p *RpcServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException)

前面講解伺服器端是如何建立的時候講到過一個processRequests()函數,它在client串連上server的時候會被server調用。我們看看源碼:

func (p *TSimpleServer) processRequests(client TTransport) error {    processor := p.processorFactory.GetProcessor(client)    //....    for {        ok, err := processor.Process(inputProtocol, outputProtocol)        if err{                  //....        }    }    return nil}

在去除無關代碼之後我們看到,伺服器首先擷取用戶端的processor,然後調用processor的Process函數,從而執行響應用戶端的請求。

看看Process函數具體是如何?的:

func (p *RpcServiceProcessor) Process(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(seqId, iprot, oprot)  //執行    }    //...}

要注意的是,函數中用紅色標註的Process是另外一個函數,這裡可不是遞迴。兩個Process函數的聲明是不一樣的:

func (p *RpcServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException)  //RpcServiceProcessor是server的processorfunc (p *rpcServiceProcessorSayHi) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) //rpcServiceProcessorSayHi是具體的handler,

現在到了最關鍵的時候了,我們看看handler是如何執行process的:

func (p *rpcServiceProcessorSayHi) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {    args := RpcServiceSayHiArgs{} //構建參數    if err = args.Read(iprot); err != nil { //讀取用戶端發來的參數        //處理err    }    iprot.ReadMessageEnd()  //讀取用戶端的結束訊息    result := RpcServiceSayHiResult{}     var retval string    var err2 error    if retval, err2 = p.handler.SayHi(args.Name); err2 != nil { //執行函數        //..處理err    } else {        result.Success = &retval    }    //...將result發送給用戶端,流程和client發送請求類似,client通過recvSayHi()函數接受result    return true, err}

現在,伺服器和用戶端究竟是如何工作的。你明白了嗎

 

轉載請註明出處,謝謝

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.