首頁 > 軟體

golang RPC包原理和使用詳細介紹

2022-09-14 22:05:07

本篇文章旨在通過學習rpc包和github上的一個rpc小專案,熟悉和學習golang中各個包的使用

工作流程

通過閱讀官方檔案,瞭解了rpc的基本工作模式

  • 第一步,建立一個用於遠端呼叫的包,存放僅供遠端呼叫使用的方法和型別-
  • 第二步,範例化包的物件,並在rpc中註冊該包,以便之後的呼叫
  • 第三步,建立一個伺服器端,接收使用者端的請求,使用編碼器解析請求後,根據請求中的方法和引數,呼叫第二步註冊的範例的方法,然後使用編碼器把返回值加密後,返回給使用者端
  • 第四步,建立一個使用者端,連線伺服器端,成功後,向連線傳送使用編碼器加密後的資料,然後等待伺服器端響應(同步或非同步)。響應成功後,使用編碼器解析伺服器端返回的資料

第三步第四步中,多次用到的編碼器,是rpc包的關鍵。預設情況下,rpc包使用的是go特有的encoding/gob包進行資料的編碼和解碼。但是當我們伺服器端和使用者端使用了不同的語言時,若加密方法無法相容,就會出現問題,所以rpc包支援自定義編碼器。

工作模式

go的rpc除了支援常規常規的tpc+埠的遠端呼叫方式,也支援基於http的遠端呼叫實現。但是,我都用rpc了,還用個毛的http形式。不過作為一種形式,我們出於禮貌的簡單瞭解下。

http模式

官方檔案的例子,就是使用的http形式的rpc,如下

伺服器端

//範例化rpc遠端呼叫的方法所屬物件
arith := new(Arith)
//註冊物件
rpc.Register(arith)
//把rpc監聽 對應到http處理器。即指定http請求addr+port時,呼叫的方法
rpc.HandleHTTP()
//獲取監聽地址
l, e := net.Listen("tcp", ":1234")
if e != nil {
	log.Fatal("listen error:", e)
}
//開啟一個go程,持續處理監聽資料
go http.Serve(l, nil)

使用者端

//連線 rpc的http伺服器端
client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
if err != nil {
	log.Fatal("dialing:", err)
}
//同步呼叫
// 範例化rpc傳入引數
args := &server.Args{7,8}
//宣告rpc 回覆引數。傳入和回覆引數,必須與呼叫方法中的參入型別一致
var reply int
//呼叫rpc註冊的方法
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
	log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d", args.A, args.B, reply)
//或:非同步呼叫
// Asynchronous call
quotient := new(Quotient)
divCall := client.Go("Arith.Divide", args, quotient, nil)
replyCall := <-divCall.Done	// will be equal to divCall

從上面的程式碼可以看到,請求伺服器端時,需要使用者端在伺服器發起http請求,如果直接在瀏覽器或者其他工具發起http請求則報錯,因為此時rpc.HandleHTTP方法指定的預設方法,使用的是預設gob編碼器,且只接收connect型別的請求。檢視原始碼,如下

直接發起的http請求,無法使用go獨有的gob包編碼,rpc伺服器端也就無法使用預設gob包解碼。

所以我們要寫一個新的方法代替rpc.HandleHTTp,把http請求繫結到一個使用其他解碼器的方法上,如下

//註冊路由和對應的handler
http.HandleFunc("/json", func(rw http.ResponseWriter, r *http.Request) {
//宣告一個使用者端連線物件   
var conn io.ReadWriteCloser = struct {
      io.Writer
      io.ReadCloser
   }{
      ReadCloser: r.Body,
      Writer:     rw,
   }
//也可以使用如下方法獲取接管使用者端連線,http處理器不在管理該連結,使用完畢後需要自行關閉連結
// conn, _, err := rw.(http.Hijacker).Hijack()
  // if err != nil {
   //   log.Print("rpc hijacking fail: ", err.Error())
    //  return
   // }
   io.WriteString(conn, "HTTP/1.0 rpc-oknn")
   //server.ServeConn(conn)
 //rpc.ServeRequest,指定編碼器,以同步的方式處理請求一次,編碼器內不關閉連結,由http服務處理。適用於http形式的請求,因為http是無狀態的,每次請求都是一個新的連結
//rpc.ServeCodec,指定編碼器,for迴圈接收使用者端連結的訊息,每次訊息處理開啟一個go程,相當於非同步處理,直到使用者端關閉或解碼錯誤,跳出迴圈,關閉連線。適用使用者端,一次連線多次傳送資料
   rpc.ServeRequest(jsonrpc.NewServerCodec(conn))})
//監聽http請求
http.ListenAndServe("127.0.0.1:1234", nil)

上面程式碼中的==jsonrpc.NewServerCodec(conn)==是一個官方定義好的json格式的編碼器。我們在http中傳送資料時,只要使用json格式,就能被伺服器端解析

執行go檔案,在post模擬url請求,如下

伺服器模式

伺服器端

//宣告和註冊rpc方法物件
//獲取監聽資訊
lis, err := net.Listen("tcp", ":8082")
	if err != nil {
		log.Fatal(err)
}
//迴圈讀取監聽到的資料
for {
	//獲取一個使用者端連線
   conn, err := lis.Accept()
   if err != nil {
      continue
   }
   //開啟一個go程式,使用自定義編碼器處理當前獲取連線
   go s.Server.ServeCodec(jsonrpc.NewServerCodec(conn))
}

使用者端

//連線伺服器端
conn, err := net.Dial("tcp", ":8082")if err != nil {
	log.Fatal(err)
}
defer conn.Close()
//使用json編碼器新建使用者端
client := &Client{rpc.NewClientWithCodec(jsonrpc.NewServerCodec(conn))}
//宣告rpc方法中傳入和輸出的引數
resq := message.ArithRequest{A: 20, B: 5}
resp := message.ArithResponse{}
//呼叫rpc方法
err = client.Call("ArithService.Add", &resq, &resp)
log.Printf("Arith.Add(%v, %v): %v ,Error: %v", resq.A, resq.B, resp.C, err)

到此這篇關於golang RPC包原理和使用詳細介紹的文章就介紹到這了,更多相關golang RPC包內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com