首頁 > 後端開發 > Golang > 主體

RPC Action EP在Go中實作一個簡單的RPC接口

PHPz
發布: 2024-09-09 18:30:11
原創
974 人瀏覽過

RPC Action EPImplement a simple RPC interface in Go

RPC(Remote procedure Call)是分散式系統中不同節點之間廣泛使用的通訊方式,是網路時代的基礎技術。 Go的標準函式庫在net/rpc套件下提供了RPC的簡單實作。本文旨在透過引導您使用 net/rpc 套件實作一個簡單的 RPC 介面來幫助您了解 RPC。

本文首刊於Medium MPP計畫。如果您是 Medium 用戶,請在 Medium 上關注我。非常感謝。

要使net/rpc中的函數能夠被遠端調用,必須滿足以下五個條件:

  • 方法的型別已匯出。
  • 方法已匯出。
  • 此方法有兩個參數,兩者都是導出(或內建)類型。
  • 此方法的第二個參數是一個指標。
  • 該方法的回傳類型為錯誤。

換句話說,函數簽章必須是:

func (t *T) MethodName(argType T1, replyType *T2) error
登入後複製

建立簡單的 RPC 請求

基於這五個條件,我們可以建構一個簡單的RPC介面:

type HelloService struct{}  
func (p *HelloService) Hello(request string, reply *string) error {  
    log.Println("HelloService Hello")  
    *reply = "hello:" + request  
    return nil  
}
登入後複製

接下來,您可以將HelloService類型的物件註冊為RPC服務:

func main() {
    _ = rpc.RegisterName("HelloService", new(HelloService))  
    listener, err := net.Listen("tcp", ":1234")  
    if err != nil {  
        log.Fatal("ListenTCP error:", err)  
    }  
    for {  
        conn, err := listener.Accept()  
        if err != nil {  
           log.Fatal("Accept error:", err)  
        }  
        go rpc.ServeConn(conn)  
    }
}
登入後複製

客戶端實作如下:

func main() {
    conn, err := net.Dial("tcp", ":1234")
    if err != nil {
        log.Fatal("net.Dial:", err)
    }
    client := rpc.NewClient(conn)
    var reply string
    err = client.Call("HelloService.Hello", "hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}
登入後複製

首先,客戶端使用 rpc.Dial 撥打 RPC 服務,然後透過 client.Call() 呼叫特定的 RPC 方法。第一個參數是RPC服務名稱和方法名稱用點組合起來,第二個是輸入,第三個是回傳值,是一個指標。這個例子展示了使用 RPC 是多麼容易。

在伺服器和客戶端程式碼中,我們都需要記住 RPC 服務名稱 HelloService 和方法名稱 Hello。這很容易導致開發過程中出現錯誤,因此我們可以透過抽象化公共部分來稍微包裝程式碼。完整程式碼如下:

// server.go
const ServerName = "HelloService"  

type HelloServiceInterface = interface {  
    Hello(request string, reply *string) error  
}  

func RegisterHelloService(srv HelloServiceInterface) error {  
    return rpc.RegisterName(ServerName, srv)  
}  

type HelloService struct{}  

func (p *HelloService) Hello(request string, reply *string) error {  
    log.Println("HelloService Hello")  
    *reply = "hello:" + request  
    return nil  
}

func main() {  
    _ = RegisterHelloService(new(HelloService))  
    listener, err := net.Listen("tcp", ":1234")  
    if err != nil {  
       log.Fatal("ListenTCP error:", err)  
    }  
    for {  
       conn, err := listener.Accept()  
       if err != nil {  
          log.Fatal("Accept error:", err)  
       }  
       go rpc.ServeConn(conn)  
    }  
}
登入後複製
// client.go

type HelloServiceClient struct {  
    *rpc.Client  
}  

var _ HelloServiceInterface = (*HelloServiceClient)(nil)  

const ServerName = "HelloService" 

func DialHelloService(network, address string) (*HelloServiceClient, error) {  
    conn, err := net.Dial(network, address)  
    client := rpc.NewClient(conn)  
    if err != nil {  
       return nil, err  
    }  
    return &HelloServiceClient{Client: client}, nil  
}

func (p *HelloServiceClient) Hello(request string, reply *string) error {  
    return p.Client.Call(ServerName+".Hello", request, reply)  
}
func main() {
    client, err := DialHelloService("tcp", "localhost:1234")  
    if err != nil {  
        log.Fatal("net.Dial:", err)  
    }  
    var reply string  
    err = client.Hello("hello", &reply)  
    if err != nil {  
        log.Fatal(err)  
    }  
    fmt.Println(reply)
}
登入後複製

是不是很眼熟?

使用 Go 的 net/rpc 套件實作 JSON 編解碼器

預設情況下,Go 的標準 RPC 函式庫使用 Go 專有的 Gob 編碼。但是,在其之上實現其他編碼(例如 Protobuf 或 JSON)非常簡單。標準函式庫已經支援jsonrpc編碼,我們可以透過對服務端和客戶端程式碼進行小改動來實作JSON編碼。

// server.go
func main() {  
    _ = rpc.RegisterName("HelloService", new(HelloService))  
    listener, err := net.Listen("tcp", ":1234")  
    if err != nil {  
       log.Fatal("ListenTCP error:", err)  
    }  
    for {  
       conn, err := listener.Accept()  
       if err != nil {  
          log.Fatal("Accept error:", err)  
       }  
       go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))  
       //go rpc.ServeConn(conn)  
    }  
}

//client.go
func DialHelloService(network, address string) (*HelloServiceClient, error) {  
    conn, err := net.Dial(network, address)  
    //client := rpc.NewClient(conn)  
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))  
    if err != nil {  
       return nil, err  
    }  
    return &HelloServiceClient{Client: client}, nil  
}
登入後複製

JSON請求資料物件內部對應兩種結構:在客戶端,是clientRequest,在伺服器端,是serverRequest。 clientRequest 和 serverRequest 結構體的內容本質上是相同的:

type clientRequest struct {  
    Method string `json:"method"`  
    Params [1]any `json:"params"`  
    Id     uint64 `json:"id"`  
}
type serverRequest struct {  
    Method string           `json:"method"`  
    Params *json.RawMessage `json:"params"`  
    Id     *json.RawMessage `json:"id"`  
}
登入後複製

這裡的Method表示由serviceName和Method組成的服務名稱。 Params第一個元素是參數,Id是呼叫者維護的唯一呼叫號,用來區分並發場景下的請求。

我們可以使用 nc 來模擬伺服器,然後運行客戶端程式碼來查看 JSON 編碼的客戶端向伺服器發送了哪些資訊:

 nc -l 1234
登入後複製

nc 指令接收以下資料:

 {"method":"HelloService.Hello","params":["hello"],"id":0}
登入後複製

這與serverRequest一致。

我們也可以運行伺服器程式碼並使用 nc 發送請求:

echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234 
--- 
{"id":1,"result":"hello:Hello","error":null}
登入後複製

結論

本文介紹了Go標準庫中的rpc包,強調了它的簡單性和強大的性能。許多第三方 rpc 函式庫都是建構在 rpc 套件之上的。本文是 RPC 研究系列的第一部分。在下一篇文章中,我們將把protobuf與RPC結合起來,最終實現我們自己的RPC框架。

以上是RPC Action EP在Go中實作一個簡單的RPC接口的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板