Go語言_RPC_Go語言的RPC

來源:互聯網
上載者:User

標籤:add   register   path   資料   不同的   parent   補丁   exe   coder   

一 標準庫的RPC

RPC(Remote Procedure Call,遠端程序呼叫)是一種通過網路從遠端電腦程式上請求服務,而不需要瞭解底層網路細節的應用程式通訊協定。簡單的說就是要像調用本地函數一樣調用伺服器的函數。

RPC協議構建於TCP或UDP,或者是 HTTP之上,允許開發人員直接調用另一台電腦上的程式,而開發人員無需額外地為這個調用過程編寫網路通訊相關代碼,使得開發包括網路分布式程式在內的應用程式更加容易.

Go語言的標準庫已經提供了RPC架構和不同的RPC實現.

下面是一個伺服器的例子:

type Echo intfunc (t *Echo) Hi(args string, reply *string) error {    *reply = "echo:" + args    return nil}func main() {    rpc.Register(new(Echo))    rpc.HandleHTTP()    l, e := net.Listen("tcp", ":1234")    if e != nil {        log.Fatal("listen error:", e)    }    http.Serve(l, nil)}

其中 rpc.Register 用於註冊RPC服務, 預設的名字是對象的類型名字(這裡是Echo). 如果需要指定特殊的名字, 可以用 rpc.RegisterName 進行註冊.

被註冊對象的類型所有滿足以下規則的方法會被匯出到RPC服務介面:

func (t *T) MethodName(argType T1, replyType *T2) error

被註冊對應至少要有一個方法滿足這個特徵, 否則可能會註冊失敗.

然後 rpc.HandleHTTP 用於指定 RPC 的傳輸協議, 這裡是採用 http 協議作為RPC調用的載體. 使用者也可以用rpc.ServeConn介面, 定製自己的傳輸協議.

用戶端可以這樣調用Echo.Hi介面:

func main() {    client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")    if err != nil {        log.Fatal("dialing:", err)    }    var args = "hello rpc"    var reply string    err = client.Call("Echo.Hi", args, &reply)    if err != nil {        log.Fatal("arith error:", err)    }    fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)}

用戶端先用rpc.DialHTTP和RPC伺服器進行一個連結(協議必須匹配).

然後通過返回的client對象進行遠程函數調用. 函數的名字是由client.Call 第一個參數指定(是一個字串).

基於HTTP的RPC調用一般是在調試時使用, 預設可以通過瀏覽"127.0.0.1:1234/debug/rpc"頁面查看RPC的統計資訊.

 

另外一個例子:

伺服器端代碼:

 

[plain] view plain copy 
  1. package main  
  2.   
  3. import (  
  4.     "errors"  
  5.     "fmt"  
  6.     "net/http"  
  7.     "net/rpc"  
  8. )  
  9.   
  10. const (  
  11.     //URL = "10.200.7.244:3545"  
  12.     URL = "127.0.0.1:3545"  
  13. )  
  14.   
  15. type Args struct {  
  16.     A, B int  
  17. }  
  18.   
  19. type Quotient struct {  
  20.     Quo, Rem int  
  21. }  
  22.   
  23. type Arith int  
  24.   
  25. func (t *Arith) Multiply(args *Args, reply *int) error {  
  26.     *reply = args.A * args.B  
  27.     return nil  
  28. }  
  29. func (t *Arith) Divide(args *Args, quo *Quotient) error {  
  30.     if args.B == 0 {  
  31.         return errors.New("divide by zero!")  
  32.     }  
  33.   
  34.     quo.Quo = args.A / args.B  
  35.     quo.Rem = args.A % args.B  
  36.   
  37.     return nil  
  38. }  
  39. func main() {  
  40.   
  41.     arith := new(Arith)  
  42.     rpc.Register(arith)  
  43.     rpc.HandleHTTP()  
  44.   
  45.     err := http.ListenAndServe(URL, nil)  
  46.     if err != nil {  
  47.         fmt.Println(err.Error())  
  48.     }  
  49.   
  50. }  



 

用戶端代碼:

 

[plain] view plain copy 
  1. package main  
  2.   
  3. import (  
  4.     "fmt"  
  5.     "net/rpc"  
  6. )  
  7.   
  8. const (  
  9.     //URL = "10.200.7.234:3545"  
  10.     URL = "127.0.0.1:3545"  
  11. )  
  12.   
  13. type Args struct {  
  14.     A, B int  
  15. }  
  16.   
  17. func main() {  
  18.   
  19.     client, err := rpc.DialHTTP("tcp", URL)  
  20.     if err != nil {  
  21.         fmt.Println(err.Error())  
  22.     }  
  23.   
  24.     args := Args{4, 4}  
  25.     var reply int  
  26.     err = client.Call("Arith.Multiply", &args, &reply)  
  27.   
  28.     if err != nil {  
  29.         fmt.Println(err.Error())  
  30.     } else {  
  31.         fmt.Println(reply)  
  32.     }  
  33. }  

 

 

 

 

[plain] view plain copy 
  1. client.Call("Arith.Multiply", &args, &reply)  

以上的方式為同步調用

 

 

非同步呼叫的代碼:

 

[plain] view plain copy 
  1. quotient := new(Quotient)  
  2. divCall := client.Go("Arith.Divide", args, "ient, nil)  
  3. replyCall := <-divCall.Done  



 

 

 

測試:

 


二  基於 JSON 的 RPC 調用

在上面的RPC例子中, 我們採用了預設的HTTP協議作為RPC調用的傳輸載體.

因為內建net/rpc包介面設計的缺陷, 我們無法使用jsonrpc等定製的編碼作為rpc.DialHTTP的底層協議. 如果需要讓jsonrpc支援rpc.DialHTTP函數, 需要調整rpc的介面.

以前有個Issue2738是針對這個問題. 我曾提交的 CL10704046 補丁用於修複這個問題. 不過因為涉及到增加rpc的介面, 官方沒有接受(因為自己重寫一個DialHTTP會更簡單).

除了傳輸協議, 還有可以指定一個RPC編碼協議, 用於編碼/節目RPC調用的函數參數和傳回值. RPC調用不指定編碼協議時, 預設採用Go語言特有的gob編碼協議.

因為, 其他語言一般都不支援Go語言的gob協議, 因此如果需要跨語言RPC調用就需要 
採用通用的編碼協議.

Go的標準庫還提供了一個"net/rpc/jsonrpc"包, 用於提供基於JSON編碼的RPC支援.

伺服器部分只需要用rpc.ServeCodec指定json編碼協議就可以了:

func main() {    lis, err := net.Listen("tcp", ":1234")    if err != nil {        return err    }    defer lis.Close()    srv := rpc.NewServer()    if err := srv.RegisterName("Echo", new(Echo)); err != nil {        return err    }    for {        conn, err := lis.Accept()        if err != nil {            log.Fatalf("lis.Accept(): %v\n", err)        }        go srv.ServeCodec(jsonrpc.NewServerCodec(conn))    }}

用戶端部分值需要用 jsonrpc.Dial 代替 rpc.Dial 就可以了:

func main() {    client, err := jsonrpc.DialHTTP("tcp", "127.0.0.1:1234")    if err != nil {        log.Fatal("dialing:", err)    }    ...}

如果需要在其他語言中使用jsonrpc和Go語言進行通訊, 需要封裝一個和jsonrpc 
匹配的庫.

關於jsonrpc的實現細節這裡就不展開講了, 感興趣的話可以參考這篇文章: JSON-RPC: a tale of interfaces.

 三 基於 Protobuf 的 RPC 調用

Protobuf 是 Google 公司開發的編碼協議. 它的優勢是編碼後的資料體積比較小(並不是壓縮演算法), 比較適合用於命令的傳輸編碼.

Protobuf 官方團隊提供 Java/C++/Python 幾個語言的支援, Go語言的版本由Go團隊提供支援, 其他語言由第三方支援.

Protobuf 的語言規範中可以定義RPC介面. 但是在Go語言和C++版本的Protobuf中都沒有產生RPC的實現.

不過作者在 Go語言版本的Protobuf基礎上開發了 RPC 的實現 protorpc, 同時提供的 protoc-gen-go命令可以產生相應的RPC代碼. 項目地址: https://code.google.com/p/protorpc/

該實現支援Go語言和C++語言, 在Protobuf官方wiki的第三方RPC實現列表中有介紹:https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns#RPC_Implementations

要使用 protorpc, 需要先在proto檔案定義介面(arith.pb/arith.proto):

package arith;// go use cc_generic_services optionoption cc_generic_services = true;message ArithRequest {    optional int32 a = 1;    optional int32 b = 2;}message ArithResponse {    optional int32 val = 1;    optional int32 quo = 2;    optional int32 rem = 3;}service ArithService {    rpc multiply (ArithRequest) returns (ArithResponse);    rpc divide (ArithRequest) returns (ArithResponse);}

protorpc使用cc_generic_services選擇控制是否輸出RPC代碼. 因此, 需要設定cc_generic_servicestrue.

然後下載 protoc-2.5.0-win32.zip, 解壓後可以得到一個 protoc.exe 的編譯命令.

然後使用下面的命令擷取 protorpc 和對應的 protoc-gen-go 外掛程式.

go get code.google.com/p/protorpcgo get code.google.com/p/protorpc/protoc-gen-go

需要確保 protoc.exe 和 protoc-gen-go.exe 都在 $PATH 中. 然後運行以下命令將前面的介面檔案轉換為Go代碼:

cd arith.pb && protoc --go_out=. arith.proto

新產生的檔案為arith.pb/arith.pb.go.

下面是基於 Protobuf-RPC 的伺服器:

package mainimport (    "errors"    "code.google.com/p/goprotobuf/proto"    "./arith.pb")type Arith intfunc (t *Arith) Multiply(args *arith.ArithRequest, reply *arith.ArithResponse) error {    reply.Val = proto.Int32(args.GetA() * args.GetB())    return nil}func (t *Arith) Divide(args *arith.ArithRequest, reply *arith.ArithResponse) error {    if args.GetB() == 0 {        return errors.New("divide by zero")    }    reply.Quo = proto.Int32(args.GetA() / args.GetB())    reply.Rem = proto.Int32(args.GetA() % args.GetB())    return nil}func main() {    arith.ListenAndServeArithService("tcp", ":1984", new(Arith))}

其中匯入的 "./arith.pb" 的名字為 arith, 在 arith.pb/arith.proto 檔案中定義(這2個可能不同名, 匯入時要小心).

arith.ArithRequestarith.ArithResponse是RPC介面的輸入和輸出參數, 也是在在arith.pb/arith.proto 檔案中定義的.

同時產生的還有一個arith.ListenAndServeArithService函數, 用於啟動RPC服務. 該函數的第三個參數是RPC的服務物件, 必須要滿足 arith.EchoService 介面的定義.

用戶端的使用也很簡單, 只要一個 arith.DialArithService 就可以連結了:

stub, client, err := arith.DialArithService("tcp", "127.0.0.1:1984")if err != nil {    log.Fatal(`arith.DialArithService("tcp", "127.0.0.1:1984"):`, err)}defer client.Close()

arith.DialArithService 返回了一個 stub 對象, 該對象已經綁定了RPC的各種方法, 可以直接調用(不需要用字串指定方法名字):

var args ArithRequestvar reply ArithResponseargs.A = proto.Int32(7)args.B = proto.Int32(8)if err = stub.Multiply(&args, &reply); err != nil {    log.Fatal("arith error:", err)}fmt.Printf("Arith: %d*%d=%d", args.GetA(), args.GetB(), reply.GetVal())

相比標準的RPC的庫, protorpc 由以下幾個優點:

  1. 採用標準的Protobuf協議, 便於和其他語言互動
  2. 內建的 protoc-gen-go 外掛程式可以產生RPC的代碼, 簡化使用
  3. 伺服器註冊和調用用戶端都是具體類型而不是字串和interface{}, 這樣可以由編譯器保證安全
  4. 底層採用了snappy壓縮傳輸的資料, 提高效率

不足之處是使用流程比標準RPC要繁複(需要將proto轉換為Go代碼).

 

四 C++ 調用 Go 提供的 Protobuf-RPC 服務

protorpc 同時也提供了 C++ 語言的實現.

C++版本的安裝如下:

  1. hg clone https://code.google.com/p/protorpc.cxx/
  2. cd protorpc.cxx
  3. build with cmake

C++ 版本 的 protorpc 對 protoc.exe 擴充了一個 
--cxx_out 選項, 用於產生RPC的代碼:

${protorpc_root}/protobuf/bin/protoc --cxx_out=. arith.proto

注:--cxx_out 選項產生的程式碼除了RPC支援外, 還有xml的序列化和還原序列化支援.

下面是 C++ 的用戶端連結 Go 語言版本的 伺服器:

#include "arith.pb.h"#include <google/protobuf/rpc/rpc_server.h>#include <google/protobuf/rpc/rpc_client.h>int main() {  ::google::protobuf::rpc::Client client("127.0.0.1", 1234);  service::ArithService::Stub arithStub(&client);  ::service::ArithRequest arithArgs;  ::service::ArithResponse arithReply;  ::google::protobuf::rpc::Error err;  // EchoService.mul  arithArgs.set_a(3);  arithArgs.set_b(4);  err = arithStub.multiply(&arithArgs, &arithReply);  if(!err.IsNil()) {    fprintf(stderr, "arithStub.multiply: %s\n", err.String().c_str());    return -1;  }  if(arithReply.c() != 12) {    fprintf(stderr, "arithStub.multiply: expected = %d, got = %d\n", 12, arithReply.c());    return -1;  }  printf("Done.\n");  return 0;}

詳細的使用說明請參考: README.md . 
更多的例子請參考: rpcserver.cc 
和 rpcclient.cc

 五 總結

Go語言的RPC用戶端是一個使用簡單, 而且功能強大的RPC庫. 基於標準的RPC庫我們可以方便的定製自己的RPC實現(傳輸協議和序列化協議都可以定製).

不過在開發 protorpc 的過程中也發現了net/rpc包的一些不足之處:

  • 內建的HTTP協議的RPC的序列化協議和傳輸協議耦合過於緊密, 使用者擴充的協議無法支援內建的HTTP傳輸協議(因為rpc.Serverrpc.Client介面缺陷導致的問題)
  • rpc.Server 只能註冊 rpc.ServerCodec, 而不能註冊工廠函數. 而jsonrpc.NewServerCodec需要依賴先建立連結(conn參數), 這樣導致了HTTP協議只能支援內建的gob協議
  • rpc.Client 的問題和 rpc.Server 類似

因為Go1需要保證API的相容性, 因此上述的問題只能希望在未來的Go2能得到改善.

Go語言_RPC_Go語言的RPC

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.