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连接这些细节,专注于业务逻辑,而且更为可靠。

07-09 02:55