[從RPC到Go-Micro 壹]Go語言實現RPC程式設計

雲程式設計師發表於2020-11-06

根據上篇內容,瞭解到RPC的原理及呼叫過程,本篇則使用Go語言的標準庫中的RPC庫實現簡單的RPC小Demo

標準庫中的RPC庫

包名: net/rpc

使用RPC庫實現簡單的計算功能

首先是提供方法暴露的一方–伺服器。

一、服務定義及暴露

在程式設計實現過程中,伺服器端需要註冊結構體物件,然後通過物件所屬的方法暴露給呼叫者,從而提供服務,該方法稱之為輸出方法,此輸出方法可以被遠端呼叫。當然,在定義輸出方法時,能夠被遠端呼叫的方法需要遵循一定的規則。我們通過程式碼進行講解:

func (t *T) MethodName(request T1,response *T2) error

上述程式碼是go語言官方給出的對外暴露的服務方法的定義標準,其中包含了主要的幾條規則,分別是:

  • 1、對外暴露的方法有且只能有兩個引數,這個兩個引數只能是輸出型別或內建型別,兩種型別中的一種。
  • 2、方法的第二個引數必須是指標型別。
  • 3、方法的返回型別為error。
  • 4、方法的型別是可輸出的。
  • 5、方法本身也是可輸出的。

我們舉例說明:假設目前我們有一個需求,給出一個float型別變數,作為圓形的半徑,要求通過RPC呼叫,返回對應的圓形面積。具體的程式設計實現思路如下:

type MathUtil struct{
}
//該方法向外暴露:提供計算圓形面積的服務
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error {
	*resp = math.Pi * req * req //圓形的面積 s = π * r * r
	return nil //返回型別
}

在上述的案例中,我們可以看到:

  • 1、Calculate方法是服務物件MathUtil向外提供的服務方法,該方法用於接收傳入的圓形半徑資料,計算圓形面積並返回。
  • 2、第一個引數req代表的是呼叫者(client)傳遞提供的引數。
  • 3、第二個引數resp代表要返回給呼叫者的計算結果,必須是指標型別。
  • 4、正常情況下,方法的返回值為是error,為nil。如果遇到異常或特殊情況,則error將作為一個字串返回給呼叫者,此時,resp引數就不會再返回給呼叫者。

至此為止,已經實現了服務端的功能定義,接下來就是讓服務程式碼生效,需要將服務進行註冊,並啟動請求處理。

二、註冊服務及監聽請求

net/rpc包為我們提供了註冊服務和處理請求的一系列方法,結合本案例實現註冊及處理邏輯,如下所示:

//1、初始化指標資料型別
mathUtil := new(MathUtil) //初始化指標資料型別

//2、呼叫net/rpc包的功能將服務物件進行註冊
err := rpc.Register(mathUtil)
if err != nil {
	panic(err.Error())
}

//3、通過該函式把mathUtil中提供的服務註冊到HTTP協議上,方便呼叫者可以利用http的方式進行資料傳遞
rpc.HandleHTTP()

//4、在特定的埠進行監聽
listen, err := net.Listen("tcp", ":9000")
if err != nil {
	panic(err.Error())
}
go http.Serve(listen, nil)

經過服務註冊和監聽處理,RPC呼叫過程中的服務端實現就已經完成了。接下來需要實現的是客戶端請求程式碼的實現。

三、客戶端呼叫

在服務端是通過Http的埠監聽方式等待連線的,因此在客戶端就需要通過http連線,首先與服務端實現連線。

  • 客戶端連線服務端

      client, err := rpc.DialHTTP("tcp", "localhost:8081")
      if err != nil {
      	panic(err.Error())
      }
    
  • 遠端方法呼叫
    客戶端成功連線服務端以後,就可以通過方法呼叫呼叫服務端的方法,具體呼叫方法如下:

    var req float32 //請求值
    req = 3
    
    var resp *float32 //返回值
    err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
    if err != nil {
    panic(err.Error())
    }
    fmt.Println(*resp)
    

    上述的呼叫方法核心在於client.Call方法的呼叫,該方法有三個引數,第一個參數列示要呼叫的遠端服務的方法名,第二個引數是呼叫時要傳入的引數,第三個引數是呼叫要接收的返回值。
    上述的Call方法呼叫實現的方式是同步的呼叫,除此之外,還有一種非同步的方式可以實現呼叫。非同步呼叫程式碼實現如下:

    var respSync *float32
    //非同步的呼叫方式
    syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil)
    replayDone := <-syncCall.Done
    fmt.Println(replayDone)
    fmt.Println(*respSync)
    
多引數的請求呼叫引數傳遞

上述內容演示了單個引數下的RPC呼叫,對於多引數下的請求該如何實現。我們通過另外一個案例進行演示。

假設現在需要實現另外一個需求:通過RPC呼叫實現計算兩個數字相加功能並返回計算結果。此時,就需要傳遞兩個引數,具體實現如下:

將引數定義在一個新的結構體中,存放在param包中:

type AddParma struct {
	Args1 float32 //第一個引數
	Args2 float32 //第二個引數
}

在server.go檔案中,實現兩數相加的功能,並實現服務註冊的邏輯:

func (mu *MathUtil) Add(param param.AddParma, resp *float32) error {
	*resp = param.Args1 + param.Args2 //實現兩數相加的功能
	return nil
}
// 1. 初始化指標資料型別
mathUtil := new(MathUtil)

// 2. 呼叫net/rpc包的功能將服務物件進行註冊
err := rpc.RegisterName("MathUtil", mathUtil)
if err != nil {
	panic(err.Error())
}

// 3. 通過該函式把mathUtil中提供的服務註冊到HTTP協議上,方便呼叫者通過http的方式呼叫
rpc.HandleHTTP()

// 4. 在特定的埠上進行監聽
listen, err := net.Listen("tcp", ":9000")
http.Serve(listen, nil)

在本案例中,我們通過新的註冊方法rpc.RegisterName實現了服務的註冊和呼叫。

完整程式碼如下

server.go
package main

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

type AddParam struct {
	Args1 float32
	Args2 float32
}

// 數學計算
type MathUtil struct {
}

// 向外暴露計算的方法
func (mu *MathUtil) Add(req AddParam, resp *float32) error {
	*resp = req.Args1 + req.Args2
	fmt.Println(*resp)
	return nil
}

// main方法
func main() {
	// 1. 初始化指標資料型別
	mathUtil := new(MathUtil)

	// 2. 呼叫net/rpc包的功能將服務物件進行註冊
	err := rpc.Register(mathUtil)
	if err != nil {
		panic(err.Error())
	}
	// 3. 通過該函式把mathUtil中提供的服務註冊到HTTP協議上,方便呼叫者通過http的方式呼叫
	rpc.HandleHTTP()

	// 4. 在特定的埠上進行監聽
	listen, err := net.Listen("tcp", ":9000")
	if err != nil {
		panic(err.Error())
	}

	_ = http.Serve(listen, nil)
}

client.go

package main

import (
	"fmt"
	"net/rpc"
)

type AddParam2 struct {
	Args1 float32
	Args2 float32
}

func main() {
	// 撥號
	client, err := rpc.DialHTTP("tcp", "localhost:9000")
	if err != nil {
		panic(err.Error())
	}

	// 請求值
	var req = AddParam2{1, 2}

	//var resp *float32
	 同步呼叫的方式
	//err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
	//if err != nil {
	//	panic(err)
	//}
	//fmt.Println(*resp)

	var respSync *float32
	//非同步的呼叫方式
	syncCall := client.Go("MathUtil.Add", req, &respSync, nil)
	replayDone := <-syncCall.Done // 堵塞,直到成功
	fmt.Println(replayDone)
	fmt.Println(*respSync)
}

參考連結

Golang - 100天從新手到大師

相關文章