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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM