[go]简单的RPC框架

将信息使用gob压缩,通过TCP进行传输实现远程服务调用。
原教程

主要分为以下几步

  1. 约定数据包传输格式
  2. 约定数据的加密和解密
  3. 服务端对请求的回复的封装
  4. 客户端对函数注册的封装
    项目结构
├── client.go
├── cmd
│   ├── client
│   │   └── main.go
│   ├── server
│   │   └── main.go
│   └── struct
│       └── user.go
├── encode.go
├── message.go
└── server.go

约定数据包传输格式

// message.go

//网络传输数据格式
//两端要约定好数据包的格式
//4字节header uint32

type Session struct {
   conn net.Conn
}

func NewSession(conn net.Conn) *Session {
   return &Session{conn: conn}
}

//对数据封包
func (s *Session) Write(data []byte) error {
   buf := make([]byte, 4+len(data))
   //写入头部,记录数据长度
   binary.BigEndian.PutUint32(buf[:4], uint32(len(data)))
   copy(buf[4:], data)
   _, err := s.conn.Write(buf)
   if err != nil {
      return err
   }
   return nil
}

//对数据拆包
func (s *Session) Read() ([]byte, error) {
   header := make([]byte, 4)
   //读头
   _, err := io.ReadFull(s.conn, header)
   if err != nil {
      return nil, err
   }
   //读数据
   dataLen := binary.BigEndian.Uint32(header)
   data := make([]byte, dataLen)
   if _, err := io.ReadFull(s.conn, data); err != nil {
      return nil, err
   }
   return data, nil
}

约定数据的加密和解密

使用gob进行压缩,适用于go程序的交互

// encode.go

// RPCData 定义RPC交互的数据结构
type RPCData struct {
   // 访问的函数
   Name string
   // 访问时的参数
   Args []interface{}
}

func NewRPCData(name string, args []interface{}) *RPCData {
   return &RPCData{
      Name: name,
      Args: args,
   }
}

//数据的压缩
func (data *RPCData) Encode() ([]byte, error) {
   var buf bytes.Buffer
   bufEnc := gob.NewEncoder(&buf)
   if err := bufEnc.Encode(data); err != nil {
      return nil, err
   }
   return buf.Bytes(), nil
}

//数据的解压
func (data *RPCData) Decode(b []byte) error {
   buf := bytes.NewBuffer(b)
   bufDec := gob.NewDecoder(buf)
   if err := bufDec.Decode(data); err != nil {
      return err
   }
   return nil
}

服务端对请求的回复的封装

// server.go

/*
服务端接收到的数据需要包括什么?
   调用的函数名、参数列表,还有一个返回值error类型
服务端需要解决的问题是什么?
   Map维护客户端传来调用函数,服务端知道去调谁
服务端的核心功能有哪些?
   维护函数map
   客户端传来的东西进行解析
   函数的返回值打包,传给客户端
*/

type Server struct {
   //监听地址
   addr string
   //map Map维护客户端传来调用函数,服务端知道去调谁
   funcs map[string]reflect.Value
}

func NewServer(addr string) *Server {
   return &Server{addr: addr, funcs: make(map[string]reflect.Value)}
}

// Register 注册服务
// 第一个参数函数名,第二个传入真正的函数
func (s *Server) Register(rpcName string, f interface{}) {
   v := reflect.ValueOf(f)
   if v.Kind() != reflect.Func {
      panic(errors.New("invalid TypeOf Func"))
   }
   if _, ok := s.funcs[rpcName]; ok {
      panic(errors.New("repeated rpcName"))
   }
   s.funcs[rpcName] = v
}

// Run 服务器等待调用的方法
func (s *Server) Run() {
   listen, err := net.Listen("tcp", s.addr)
   if err != nil {
      panic(err)
   }
   for {
      conn, err := listen.Accept()
      if err != nil {
         continue
      }
      go s.process(conn)
   }
}

func (s *Server) process(conn net.Conn) {
   session := NewSession(conn)
   session.conn.SetDeadline(time.Time{})
   for {
      b, err := session.Read()
      if err == io.EOF {
         log.Println("EOF received")
         return
      }
      if err != nil {
         log.Println("Error reading session,err:", err)
         return
      }
      rpcData := new(RPCData)
      if err := rpcData.Decode(b); err != nil {
         log.Println("Error decoding session,err:", err)
         return
      }
      f, ok := s.funcs[rpcData.Name]
      if !ok {
         log.Println("Err Call Func Not Find,func name:", rpcData.Name)
         return
      }
      //通过反射调用函数
      t := f.Type()
      if t.NumIn() != len(rpcData.Args) {
         log.Println("Err NumIn Lens Not Equal")
         return
      }
      inArgs := make([]reflect.Value, 0, len(rpcData.Args))
      for i, arg := range rpcData.Args {
         v := reflect.ValueOf(arg)
         if t.In(i).Kind() != v.Type().Kind() {
            log.Println("Err parameter Type Mismatch,need:", t.In(i).Kind(), "but:", v.Type().Kind())
            return
         }
         inArgs = append(inArgs, v)
      }
      out := f.Call(inArgs)
      outArgs := make([]interface{}, 0, len(out))
      for _, o := range out {
         outArgs = append(outArgs, o.Interface())
      }
      //响应内容
      responseRPCData := NewRPCData(rpcData.Name, outArgs)
      bytes, err := responseRPCData.Encode()
      if err != nil {
         log.Println("Encode error,err:", err)
         return
      }
      if err := session.Write(bytes); err != nil {
         log.Println("Send error,err:", err)
      }
   }
}

客户端对函数注册的封装

// client.go

type Client struct {
   conn net.Conn
}

func NewClient(conn net.Conn) *Client {
   return &Client{conn: conn}
}

// CallRPC 实现通用的RPC客户端
// 传入访问的函数名
// fPtr指向的是函数原型
// var select fun xx(User)
// cli.callRPC("selectUser",&select)
func (c *Client) CallRPC(rpcName string, fPtr interface{}) chan error {
   //获取函数原型
   fn := reflect.ValueOf(fPtr).Elem()
   if fn.Type().Kind() != reflect.Func {
      panic(errors.New("err fPtr Not Func"))
   }
   errChan := make(chan error, 1)
   //远程函数调用
   f := func(args []reflect.Value) []reflect.Value {
      inArgs := make([]interface{}, 0, len(args))
      for _, arg := range args {
         inArgs = append(inArgs, arg.Interface())
      }
      session := NewSession(c.conn)
      rpcData := NewRPCData(rpcName, inArgs)
      b, err := rpcData.Encode()
      if err != nil {
         errChan <- errors.New("Err Encode err:" + err.Error())
         return nil
      }
      if err = session.Write(b); err != nil {
         errChan <- err
         return nil
      }
      respBytes, err := session.Read()
      if err != nil {
         errChan <- err
         return nil
      }
      respRPC := new(RPCData)
      if err := respRPC.Decode(respBytes); err != nil {
         errChan <- err
         return nil
      }
      outArgs := make([]reflect.Value, 0, len(respRPC.Args))
      for i, arg := range respRPC.Args {
         //nil转换
         if arg == nil {
            // reflect.Zero()会返回类型的零值的value
            // .out()会返回函数输出的参数类型
            outArgs = append(outArgs, reflect.Zero(fn.Type().Out(i)))
            continue
         }
         outArgs = append(outArgs, reflect.ValueOf(arg))
      }
      errChan <- nil
      return outArgs
   }
   v := reflect.MakeFunc(fn.Type(), f)
   fn.Set(v)
   return errChan
}

测试

// cmd/struct/user.go 通用结构

package ss

type User struct {
   Name string
   Age  int
}
// cmd/server/main.go

func SayHello(user ss.User) string {
   return fmt.Sprintf("hello %s:%d", user.Name, user.Age)
}

func main() {
   log.Println("server starting...")
   gob.Register(ss.User{}) //传输时会使用空接口传输,所以需要注册下
   srv := rpc.NewServer(":8080")
   srv.Register("SayHello", SayHello)
   srv.Run()
}
// cmd/client/main.go

func main() {
   conn, err := net.Dial("tcp", ":8080")
   if err != nil {
      log.Fatalln("can't dial: ", err)
   }
   gob.Register(ss.User{})
   cli := rpc.NewClient(conn)
   var SayHello func(user ss.User) string
   errChan := cli.CallRPC("SayHello", &SayHello)
   defer close(errChan)
   res := SayHello(ss.User{Name: "raja", Age: 20})
   fmt.Println(res, <-errChan)
   res = SayHello(ss.User{Name: "ww", Age: 21})
   fmt.Println(res, <-errChan)
}

image.png

总结

一个简单的RPC框架,对go的反射使用更加熟悉了一些

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值