1. 程式人生 > 其它 >Go語言下RPC的開發

Go語言下RPC的開發

一、rpc之HelloWorld

Go語言的rpc包的路徑為net/rpc:

1、server.go

package main

import (
    "net"
    "net/rpc"
)

type HelloWorldService struct {
}

func (s *HelloWorldService) HelloWorld(request string, response *string) error {
    *response = "hello" + request
    return nil
}

func main() {
    _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{})
    listener, err :
= net.Listen("tcp", ":8000") if err != nil { panic("監聽埠失敗!") } conn, err := listener.Accept() if err != nil { panic("建立連線失敗!") } rpc.ServeConn(conn) }
  • HelloWorld方法需要滿足Go語言RPC規則,接收兩個引數,第二個引數是指標型別,並且返回一個error型別,同時必須是公開方法
  • HelloWorldService型別的物件註冊到RPC服務
  • rpc.register函式呼叫會將物件型別中滿足RPC規則的物件方法註冊為RPC函式,所有註冊的方法會放置在HelloWorldService服務空間下
  • 建立TCP連結,通過rpc.ServerConn函式在該連結上提供RPC服務

1、client.go

package main

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

func main() {
    client, err := rpc.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        log.Fatal("dial", err)
    }
    var response string
    err = client.Call("HelloWorldService.HelloWorld", "world", &response)
    
if err != nil { log.Fatal("caller", err) } fmt.Println(response) }
  • 通過rpc.Dial進行RPC撥號服務
  • 通過client.Call呼叫具體方法
  • 方法中第一個引數是RPC服務名稱和方法名稱,第二個和第三個引數是方法中傳入的實參

二、基於json實現RPC

上述RPC預設採用的是Go語言特有的gob編碼,所以其它語言呼叫Go語言實現的RPC服務相對困難。比較常見的有基於json實現的RPC服務,所以如果使用json來替換gob將會使其通用性變的更強。在Go語言中可以通過net/rpc/jsonrpc來基於json實現RPC,實現跨語言呼叫。

1、server.go

package main

import (
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type HelloWorldService struct {
}

func (s *HelloWorldService) HelloWorld(request string, response *string) error {
    *response = "hello" + request
    return nil
}

func main() {
    _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{})
    listener, err := net.Listen("tcp", ":8000")
    if err != nil {
        panic("監聽埠失敗!")
    }
    // 不斷的接收新的請求
    for {
        conn, err := listener.Accept()
        if err != nil {
            panic("建立連線失敗!")
        }
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 啟動協程處理請求
    }

}

這與之前相比使用rpc.ServeCodec函式替代了rpc.ServeConn函式,傳入符合json編碼的引數。

2、client.go

package main

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

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        log.Fatal("dial", err)
    }
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    var response string
    err = client.Call("HelloWorldService.HelloWorld", "world", &response)
    if err != nil {
        log.Fatal("caller", err)
    }
    fmt.Println(response)

}

這與之前相比通過net進行撥號建立連線,然後通過NewClientWithCodec函式傳入符合json編碼的引數。

既然使用的是json進行編、解碼,那麼就具備一定的通用性,可以使用其它語言來進行呼叫,比如使用Python客戶端進行呼叫,但是Go的RPC監聽的是TCP連線,如果使用Python中的requests包是行不通的,它使用的是HTTP協議,會攜帶很多比如請求頭之類的多餘資訊,所以使用socket程式設計傳送請求:

3、client.py

import json
import socket

# 傳送的資料格式必須滿足這樣的
"""
method: 服務名稱、方法名稱
result:返回的結果
id:隨意指定一個值,如果不指定,返回值id為None
"""
request = {
    "method": "HelloWorldService.HelloWorld",
    "params": ["bily"],
    "id": 0
}

client = socket.create_connection(("127.0.0.1", 8000))
client.sendall(json.dumps(request).encode())

# 設定一次性接收的資料大小
response = client.recv(4096)
response = json.loads(response.decode())
print(response)

# 關閉連線
client.close()

請求結果:

{'id': 0, 'result': 'hellobily', 'error': None}

三、基於http實現RPC

1、server.go

package main

import (
    "io"
    "net/http"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type HelloWorldService struct {
}

func (s *HelloWorldService) HelloWorld(request string, response *string) error {
    *response = "hello" + request
    return nil
}

func main() {
    _ = rpc.RegisterName("HelloWorldService", new(HelloWorldService))
    http.HandleFunc("/jsonrpc", func(writer http.ResponseWriter, request *http.Request) {
        var conn io.ReadWriteCloser = struct {
            io.Writer
            io.ReadCloser
        }{
            ReadCloser: request.Body,
            Writer:     writer,
        }
        _ = rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
    })
    _ = http.ListenAndServe(":8000", nil)

}

2、client.go

import requests

request = {
    "method": "HelloWorldService.HelloWorld",
    "params": ["karry"],
    "id": 0
}

res = requests.post("http://127.0.0.1:8000/jsonrpc", json=request)
print(res.text)

四、封裝代理

在之前的呼叫中,存在一些明顯的問題:

  • 服務端中,業務程式碼與RPC通訊底層混在一起,需要一種結構層次,將其分離
  • 客戶端中,每次呼叫都需要知道業務端的服務名稱與方法名稱,將其單獨封裝

這樣就引出服務端代理Server Stub和客戶端代理Client Stub:

1、目錄結構

├─client
│      client.go
│
├─client_stub
│      client_stub.go
│
├─handler
│      handler.go
│
├─server
│      server.go
│
└─server_stub
        server_stub.go

2、client.go

package main

import (
    "fmt"
    "go_rpc_project/stub_rpc/client_stub"
)

func main() {
    // 建立連線
    client := client_stub.NewHelloWorldServiceClient("tcp", "127.0.0.1:8000")
    // 呼叫業務函式HelloWorld
    var response string
    _ = client.HelloWorld("harry", &response)
    fmt.Println(response)

}

3、client_stub.go

package client_stub

import (
    "go_rpc_project/stub_rpc/handler"
    "log"
    "net/rpc"
)

type HelloWorldServiceStub struct {
    *rpc.Client
}

func NewHelloWorldServiceClient(protcol string, address string) HelloWorldServiceStub {
    conn, err := rpc.Dial(protcol, address)
    if err != nil {
        log.Fatal("撥號錯誤", err)
    }
    return HelloWorldServiceStub{conn}
}

func (c *HelloWorldServiceStub) HelloWorld(request string, response *string) error {
    err := c.Call(handler.HelloWorldServiceName+".HelloWorld", request, response)
    if err != nil {
        log.Fatal("呼叫服務失敗", err)
    }
    return nil
}

4、handler.go

package handler

const HelloWorldServiceName = "handler/HelloWorldService"

type HelloWorldService struct {
}

func (s *HelloWorldService) HelloWorld(request string, response *string) error {
    *response = "hello" + request
    return nil
}

5、server.go

package main

import (
    "go_rpc_project/stub_rpc/handler"
    "go_rpc_project/stub_rpc/server_stub"
    "net"
    "net/rpc"
)

func main() {
    // 例項化一個server
    listener, _ := net.Listen("tcp", ":8000")
    // 註冊,將業務邏輯程式碼註冊到代理中
    _ = server_stub.RegisterServicer(&handler.HelloWorldService{})
    // 啟動服務
    for {
        conn, _ := listener.Accept()
        go rpc.ServeConn(conn)
    }
}

6、server_stub.go

package server_stub

import (
    "go_rpc_project/stub_rpc/handler"
    "net/rpc"
)

type HelloWorldServicer interface {
    HelloWorld(request string, response *string) error
}

func RegisterServicer(srv HelloWorldServicer) error {
    return rpc.RegisterName(handler.HelloWorldServiceName, srv)
}
作者:iveBoy 出處:http://www.cnblogs.com/shenjianping/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連線,否則保留追究法律責任的權利。