動手實現一個簡單的 rpc 框架到入門 grpc(上)

lukedever發表於2020-07-21

rpc 全稱 Remote Procedure Call 遠端過程呼叫,即呼叫遠端方法。我們呼叫當前程式中的方法時很簡單,但是想要呼叫不同程式,甚至不同主機、不同語言中的方法時就需要藉助 rpc 來實現,下面我一步步實現一個簡單的 rpc 呼叫。

server 端註冊函式,執行並接收客戶端請求

func main() {
    srv := NewServer()
    srv.Register("fn", fn)
    srv.Run()
}
//為了簡單,這裡只需要接收到訊息列印出就代表執行成功
func fn(args ...interface{}) {
    fmt.println(args)
}

定義請求格式

type rpcData struct {
    Name string         //函式名
    Args []interface{}  //引數
}

server 執行起來後,接收 socket 請求,解析訊息呼叫已註冊的函式

//server結構體
type server struct {
    conn net.Conn                   //socket連線
    maps map[string]reflect.Value   //函式字典
}
//建構函式
func NewServer() *server {
    return &server{
        maps: make(map[string]reflect.Value),
    }
}
//註冊函式
func (s *server) Register(fname string, fun interface{}) {
    if _, ok := s.maps[fname]; !ok {
        s.maps[fname] = reflect.ValueOf(fun)
    }
}
//執行一個socket接收請求
func (s *server) Run() {
    listen, err := net.Listen("tcp4", ":3001")
    if err != nil {
        panic(err)
    }
    for {
        s.conn, err = listen.Accept()
        if err != nil {
            continue
        }
        go s.handleConnect()
    }
}

處理請求時,這裡為了簡單我使用 json 解析,同時需要定義一個簡單的協議:客戶端傳送時,前4個位元組放置訊息長度,這樣服務端接收到時就能知道訊息的長度,從而正常解碼訊息

func (s *server) handleConnect() {
    for {
        header := make([]byte, 4)
        if _, err := s.conn.Read(header); err != nil {
            continue
        }
        bodyLen := binary.BigEndian.Uint32(header)
        body := make([]byte, int(bodyLen))
        if _, err := s.conn.Read(body); err != nil {
            continue
        }
        var req rpcData
        if err := json.Unmarshal(body, &req); err != nil {
            continue
        }
        inArgs := make([]reflect.Value, len(req.Args))
        for i := range req.Args {
            inArgs[i] = reflect.ValueOf(req.Args[i])
        }
        fn := s.maps[req.Name]
        fn.Call(inArgs)
    }
}

client 端只需呼叫函式,透過網路傳送請求

func main() {
    var req = rpcData{"fn", []interface{}{1, "aaa"}}
    rpcCall(req)
}

func rpcCall(data rpcData) {
    conn, err := net.Dial("tcp4", "127.0.0.1:3001")
    if err != nil {
        panic(err)
    }
    req, err := json.Marshal(data)
    if err != nil {
        panic(err)
    }
    buf := make([]byte, 4+len(req))
    binary.BigEndian.PutUint32(buf[:4], uint32(len(req)))
    copy(buf[4:], req)
    _, err = conn.Write(buf)
    if err != nil {
        panic(err)
    }
}

測試時,首先執行 server,然後執行 client,只要看到正確的列印就代表呼叫成功,這就是一個最簡單(簡陋)的 rpc 了。

當我們使用 grpc 這些 rpc 框架時,就可以不用自己實現訊息編碼解碼、socket連線這些細節,專注於業務邏輯,而且更為可靠。

參考: github.com/ankur-anand/simple-go-r...

原文:www.cnblogs.com/luke44/p/13267700....

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章