<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          golang實(shí)現(xiàn)RPC的幾種方式

          共 7379字,需瀏覽 15分鐘

           ·

          2022-01-14 15:27

          什么是RPC

          遠(yuǎn)程過程調(diào)用(Remote Procedure Call,縮寫為 RPC)是一個計(jì)算機(jī)通信協(xié)議。該協(xié)議允許運(yùn)行于一臺計(jì)算機(jī)的程序調(diào)用另一臺計(jì)算機(jī)的子程序,而程序員無需額外地為這個交互作用編程。如果涉及的軟件采用面向?qū)ο缶幊蹋敲催h(yuǎn)程過程調(diào)用亦可稱作遠(yuǎn)程調(diào)用或遠(yuǎn)程方法調(diào)用。

          用通俗易懂的語言描述就是:RPC允許跨機(jī)器、跨語言調(diào)用計(jì)算機(jī)程序方法。打個比方,我用go語言寫了個獲取用戶信息的方法getUserInfo,并把go程序部署在阿里云服務(wù)器上面,現(xiàn)在我有一個部署在騰訊云上面的php項(xiàng)目,需要調(diào)用golang的getUserInfo方法獲取用戶信息,php跨機(jī)器調(diào)用go方法的過程就是RPC調(diào)用。

          golang中如何實(shí)現(xiàn)RPC

          在golang中實(shí)現(xiàn)RPC非常簡單,有封裝好的官方庫和一些第三方庫提供支持。Go RPC可以利用tcp或http來傳遞數(shù)據(jù),可以對要傳遞的數(shù)據(jù)使用多種類型的編解碼方式。golang官方的net/rpc庫使用encoding/gob進(jìn)行編解碼,支持tcphttp數(shù)據(jù)傳輸方式,由于其他語言不支持gob編解碼方式,所以使用net/rpc庫實(shí)現(xiàn)的RPC方法沒辦法進(jìn)行跨語言調(diào)用。

          golang官方還提供了net/rpc/jsonrpc庫實(shí)現(xiàn)RPC方法,JSON RPC采用JSON進(jìn)行數(shù)據(jù)編解碼,因而支持跨語言調(diào)用。但目前的jsonrpc庫是基于tcp協(xié)議實(shí)現(xiàn)的,暫時不支持使用http進(jìn)行數(shù)據(jù)傳輸。

          除了golang官方提供的rpc庫,還有許多第三方庫為在golang中實(shí)現(xiàn)RPC提供支持,大部分第三方rpc庫的實(shí)現(xiàn)都是使用protobuf進(jìn)行數(shù)據(jù)編解碼,根據(jù)protobuf聲明文件自動生成rpc方法定義與服務(wù)注冊代碼,在golang中可以很方便的進(jìn)行rpc服務(wù)調(diào)用。

          net/rpc庫

          下面的例子演示一下如何使用golang官方的net/rpc庫實(shí)現(xiàn)RPC方法,使用http作為RPC的載體,通過net/http包監(jiān)聽客戶端連接請求。

          $GOPATH/src/test/rpc/rpc_server.go

          package?main

          import?(
          ????"errors"
          ????"fmt"
          ????"log"
          ????"net"
          ????"net/http"
          ????"net/rpc"
          ????"os"
          )

          //?算數(shù)運(yùn)算結(jié)構(gòu)體
          type?Arith?struct?{
          }

          //?算數(shù)運(yùn)算請求結(jié)構(gòu)體
          type?ArithRequest?struct?{
          ????A?int
          ????B?int
          }

          //?算數(shù)運(yùn)算響應(yīng)結(jié)構(gòu)體
          type?ArithResponse?struct?{
          ????Pro?int?//?乘積
          ????Quo?int?//?商
          ????Rem?int?//?余數(shù)
          }

          //?乘法運(yùn)算方法
          func?(this?*Arith)?Multiply(req?ArithRequest,?res?*ArithResponse)?error?{
          ????res.Pro?=?req.A?*?req.B
          ????return?nil
          }

          //?除法運(yùn)算方法
          func?(this?*Arith)?Divide(req?ArithRequest,?res?*ArithResponse)?error?{
          ????if?req.B?==?0?{
          ????????return?errors.New("divide?by?zero")
          ????}
          ????res.Quo?=?req.A?/?req.B
          ????res.Rem?=?req.A?%?req.B
          ????return?nil
          }

          func?main()?{
          ????rpc.Register(new(Arith))?//?注冊rpc服務(wù)
          ????rpc.HandleHTTP()?????????//?采用http協(xié)議作為rpc載體

          ????lis,?err?:=?net.Listen("tcp",?"127.0.0.1:8095")
          ????if?err?!=?nil?{
          ????????log.Fatalln("fatal?error:?",?err)
          ????}

          ????fmt.Fprintf(os.Stdout,?"%s",?"start?connection")

          ????http.Serve(lis,?nil)
          }

          上述服務(wù)端程序運(yùn)行后,將會監(jiān)聽本地的8095端口,我們可以實(shí)現(xiàn)一個客戶端程序,連接服務(wù)端并實(shí)現(xiàn)RPC方法調(diào)用。

          $GOPATH/src/test/rpc/rpc_client.go

          package?main

          import?(
          ????"fmt"
          ????"log"
          ????"net/rpc"
          )

          //?算數(shù)運(yùn)算請求結(jié)構(gòu)體
          type?ArithRequest?struct?{
          ????A?int
          ????B?int
          }

          //?算數(shù)運(yùn)算響應(yīng)結(jié)構(gòu)體
          type?ArithResponse?struct?{
          ????Pro?int?//?乘積
          ????Quo?int?//?商
          ????Rem?int?//?余數(shù)
          }

          func?main()?{
          ????conn,?err?:=?rpc.DialHTTP("tcp",?"127.0.0.1:8095")
          ????if?err?!=?nil?{
          ????????log.Fatalln("dailing?error:?",?err)
          ????}

          ????req?:=?ArithRequest{9,?2}
          ????var?res?ArithResponse

          ????err?=?conn.Call("Arith.Multiply",?req,?&res)?//?乘法運(yùn)算
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error:?",?err)
          ????}
          ????fmt.Printf("%d?*?%d?=?%d\n",?req.A,?req.B,?res.Pro)

          ????err?=?conn.Call("Arith.Divide",?req,?&res)
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error:?",?err)
          ????}
          ????fmt.Printf("%d?/?%d,?quo?is?%d,?rem?is?%d\n",?req.A,?req.B,?res.Quo,?res.Rem)
          }


          net/rpc/jsonrpc庫

          上面的例子我們演示了使用net/rpc實(shí)現(xiàn)RPC的過程,但是沒辦法在其他語言中調(diào)用上面例子實(shí)現(xiàn)的RPC方法。所以接下來的例子我們演示一下使用net/rpc/jsonrpc庫實(shí)現(xiàn)RPC方法,此方式實(shí)現(xiàn)的RPC方法支持跨語言調(diào)用。

          $GOPATH/src/test/rpc/jsonrpc_server.go

          package?main

          import?(
          ????"errors"
          ????"fmt"
          ????"log"
          ????"net"
          ????"net/rpc"
          ????"net/rpc/jsonrpc"
          ????"os"
          )

          //?算數(shù)運(yùn)算結(jié)構(gòu)體
          type?Arith?struct?{
          }

          //?算數(shù)運(yùn)算請求結(jié)構(gòu)體
          type?ArithRequest?struct?{
          ????A?int
          ????B?int
          }

          //?算數(shù)運(yùn)算響應(yīng)結(jié)構(gòu)體
          type?ArithResponse?struct?{
          ????Pro?int?//?乘積
          ????Quo?int?//?商
          ????Rem?int?//?余數(shù)
          }

          //?乘法運(yùn)算方法
          func?(this?*Arith)?Multiply(req?ArithRequest,?res?*ArithResponse)?error?{
          ????res.Pro?=?req.A?*?req.B
          ????return?nil
          }

          //?除法運(yùn)算方法
          func?(this?*Arith)?Divide(req?ArithRequest,?res?*ArithResponse)?error?{
          ????if?req.B?==?0?{
          ????????return?errors.New("divide?by?zero")
          ????}
          ????res.Quo?=?req.A?/?req.B
          ????res.Rem?=?req.A?%?req.B
          ????return?nil
          }

          func?main()?{
          ????rpc.Register(new(Arith))?//?注冊rpc服務(wù)

          ????lis,?err?:=?net.Listen("tcp",?"127.0.0.1:8096")
          ????if?err?!=?nil?{
          ????????log.Fatalln("fatal?error:?",?err)
          ????}

          ????fmt.Fprintf(os.Stdout,?"%s",?"start?connection")

          ????for?{
          ????????conn,?err?:=?lis.Accept()?//?接收客戶端連接請求
          ????????if?err?!=?nil?{
          ????????????continue
          ????????}

          ????????go?func(conn?net.Conn)?{?//?并發(fā)處理客戶端請求
          ????????????fmt.Fprintf(os.Stdout,?"%s",?"new?client?in?coming\n")
          ????????????jsonrpc.ServeConn(conn)
          ????????}(conn)
          ????}
          }

          上述服務(wù)端程序啟動后,將會監(jiān)聽本地的8096端口,并處理客戶端的tcp連接請求。我們可以用golang實(shí)現(xiàn)一個客戶端程序連接上述服務(wù)端并進(jìn)行RPC調(diào)用。

          $GOPATH/src/test/rpc/jsonrpc_client.go

          package?main

          import?(
          ????"fmt"
          ????"log"
          ????"net/rpc/jsonrpc"
          )

          //?算數(shù)運(yùn)算請求結(jié)構(gòu)體
          type?ArithRequest?struct?{
          ????A?int
          ????B?int
          }

          //?算數(shù)運(yùn)算響應(yīng)結(jié)構(gòu)體
          type?ArithResponse?struct?{
          ????Pro?int?//?乘積
          ????Quo?int?//?商
          ????Rem?int?//?余數(shù)
          }

          func?main()?{
          ????conn,?err?:=?jsonrpc.Dial("tcp",?"127.0.0.1:8096")
          ????if?err?!=?nil?{
          ????????log.Fatalln("dailing?error:?",?err)
          ????}

          ????req?:=?ArithRequest{9,?2}
          ????var?res?ArithResponse

          ????err?=?conn.Call("Arith.Multiply",?req,?&res)?//?乘法運(yùn)算
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error:?",?err)
          ????}
          ????fmt.Printf("%d?*?%d?=?%d\n",?req.A,?req.B,?res.Pro)

          ????err?=?conn.Call("Arith.Divide",?req,?&res)
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error:?",?err)
          ????}
          ????fmt.Printf("%d?/?%d,?quo?is?%d,?rem?is?%d\n",?req.A,?req.B,?res.Quo,?res.Rem)
          }


          protorpc庫

          為了實(shí)現(xiàn)跨語言調(diào)用,在golang中實(shí)現(xiàn)RPC方法的時候我們應(yīng)該選擇一種跨語言的數(shù)據(jù)編解碼方式,比如JSON,上述的jsonrpc可以滿足此要求,但是也存在一些缺點(diǎn),比如不支持http傳輸,數(shù)據(jù)編解碼性能不高等。于是呢,一些第三方rpc庫都選擇采用protobuf進(jìn)行數(shù)據(jù)編解碼,并提供一些服務(wù)注冊代碼自動生成功能。下面的例子我們使用protobuf來定義RPC方法及其請求響應(yīng)參數(shù),并使用第三方的protorpc庫來生成RPC服務(wù)注冊代碼。

          首先,需要安裝protobufprotoc可執(zhí)行命令,可以參考此篇文章:protobuf快速上手指南

          然后,我們編寫一個proto文件,定義要實(shí)現(xiàn)的RPC方法及其相關(guān)參數(shù)。

          $GOPATH/src/test/rpc/pb/arith.proto

          syntax?=?"proto3";package?pb;??//?算術(shù)運(yùn)算請求結(jié)構(gòu)
          message?ArithRequest?{????int32?a?=?1;????int32?b?=?2;}//?算術(shù)運(yùn)算響應(yīng)結(jié)構(gòu)

          message?ArithResponse?{????
          ????int32?pro?=?1;??//?乘積
          ????int32?quo?=?2;??//?商
          ????int32?rem?=?3;??//?余數(shù)
          }//?rpc方法

          service?ArithService?{????
          rpc?multiply?(ArithRequest)?returns?(ArithResponse);??//?乘法運(yùn)算方法
          rpc?divide?(ArithRequest)?returns?(ArithResponse);???//?除法運(yùn)算方法
          }

          接下來我們需要根據(jù)上述定義的arith.proto文件生成RPC服務(wù)代碼。要先安裝protorpc庫:go get github.com/chai2010/protorpc?然后使用protoc工具生成代碼:protoc --go_out=plugin=protorpc=. arith.proto?執(zhí)行protoc命令后,在與arith.proto文件同級的目錄下生成了一個arith.pb.go文件,里面包含了RPC方法定義和服務(wù)注冊的代碼。

          基于生成的arith.pb.go代碼我們來實(shí)現(xiàn)一個rpc服務(wù)端

          $GOPATH/src/test/rpc/protorpc_server.go

          package?main

          import?(
          ????"errors"
          ????"test/rpc/pb"
          )

          //?算術(shù)運(yùn)算結(jié)構(gòu)體
          type?Arith?struct?{
          }

          //?乘法運(yùn)算方法
          func?(this?*Arith)?Multiply(req?*pb.ArithRequest,?res?*pb.ArithResponse)?error?{
          ????res.Pro?=?req.GetA()?*?req.GetB()
          ????return?nil
          }

          //?除法運(yùn)算方法
          func?(this?*Arith)?Divide(req?*pb.ArithRequest,?res?*pb.ArithResponse)?error?{
          ????if?req.GetB()?==?0?{
          ????????return?errors.New("divide?by?zero")
          ????}
          ????res.Quo?=?req.GetA()?/?req.GetB()
          ????res.Rem?=?req.GetA()?%?req.GetB()
          ????return?nil
          }

          func?main()?{
          ????pb.ListenAndServeArithService("tcp",?"127.0.0.1:8097",?new(Arith))
          }

          運(yùn)行上述程序,將會監(jiān)聽本地的8097端口并接收客戶端的tcp連接。

          基于ariti.pb.go再來實(shí)現(xiàn)一個客戶端程序。

          $GOPATH/src/test/protorpc_client.go

          package?main

          import?(
          ????"fmt"
          ????"log"
          ????"test/rpc/pb"
          )

          func?main()?{
          ????conn,?err?:=?pb.DialArithService("tcp",?"127.0.0.1:8097")
          ????if?err?!=?nil?{
          ????????log.Fatalln("dailing?error:?",?err)
          ????}
          ????defer?conn.Close()

          ????req?:=?&pb.ArithRequest{9,?2}

          ????res,?err?:=?conn.Multiply(req)
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error:?",?err)
          ????}
          ????fmt.Printf("%d?*?%d?=?%d\n",?req.GetA(),?req.GetB(),?res.GetPro())

          ????res,?err?=?conn.Divide(req)
          ????if?err?!=?nil?{
          ????????log.Fatalln("arith?error?",?err)
          ????}
          ????fmt.Printf("%d?/?%d,?quo?is?%d,?rem?is?%d\n",?req.A,?req.B,?res.Quo,?res.Rem)
          }


          如何跨語言調(diào)用golang的RPC方法

          上面的三個例子,我們分別使用net/rpcnet/rpc/jsonrpcprotorpc實(shí)現(xiàn)了golang中的RPC服務(wù)端,并給出了對應(yīng)的golang客戶端RPC調(diào)用示例,因?yàn)镴SON和protobuf是支持多語言的,所以使用jsonrpcprotorpc實(shí)現(xiàn)的RPC方法我們是可以在其他語言中進(jìn)行調(diào)用的。下面給出一個php客戶端程序,通過socket連接調(diào)用jsonrpc實(shí)現(xiàn)的服務(wù)端RPC方法。

          $PHPROOT/jsonrpc.php



          class?JsonRPC?{

          ????private?$conn;

          ????function?__construct($host,?$port)?{
          ????????$this->conn?=?fsockopen($host,?$port,?$errno,?$errstr,?3);
          ????????if?(!$this->conn)?{
          ????????????return?false;
          ????????}
          ????}

          ????public?function?Call($method,?$params)?{
          ????????if?(!$this->conn)?{
          ????????????return?false;
          ????????}
          ????????$err?=?fwrite($this->conn,?json_encode(array(
          ????????????????'method'?=>?$method,
          ????????????????'params'?=>?array($params),
          ????????????????'id'?????=>?0,
          ????????????))."\n");
          ????????if?($err?===?false)?{
          ????????????return?false;
          ????????}
          ????????stream_set_timeout($this->conn,?0,?3000);
          ????????$line?=?fgets($this->conn);
          ????????if?($line?===?false)?{
          ????????????return?NULL;
          ????????}
          ????????return?json_decode($line,true);
          ????}
          }

          $client?=?new?JsonRPC("127.0.0.1",?8096);
          $args?=?array('A'=>9,?'B'=>2);
          $r?=?$client->Call("Arith.Multiply",?$args);
          printf("%d?*?%d?=?%d\n",?$args['A'],?$args['B'],?$r['result']['Pro']);
          $r?=?$client->Call("Arith.Divide",?array('A'=>9,?'B'=>2));
          printf("%d?/?%d,?Quo?is?%d,?Rem?is?%d\n",?$args['A'],?$args['B'],?$r['result']['Quo'],?$r['result']['Rem']);


          其他RPC庫

          除了上面提到的三種在golang實(shí)現(xiàn)RPC的方式外,還有一些其他的rpc庫提供了類似的功能,比較出名的有g(shù)oogle開源的grpc,但是grpc的初次安裝比較麻煩,這里就不做進(jìn)一步介紹了,有興趣的可以自己了解。

          瀏覽 51
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产亚洲欧美精品久久久久久 | 天天色天天干天天日 | 免费看黄片网站 | 亚洲成人网站无码在线观看 | 撸综合|