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

luke44發表於2020-07-08

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連線這些細節,專注於業務邏輯,而且更為可靠。

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

相關文章