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