golang自带的rpc 服务端

本文详细介绍了RPC服务端如何通过register()方法注册待远程调用的方法,并通过反射机制确保方法符合调用规范。此外,还阐述了如何解析HTTP请求以定位具体的服务及方法,最终实现远程调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务端通过register()方法将需要被远程调用的方法注册到map中。

func (server *Server) register(rcvr interface{}, name string, useName bool) error {
   s := new(service)
   s.typ = reflect.TypeOf(rcvr)
   s.rcvr = reflect.ValueOf(rcvr)
   sname := reflect.Indirect(s.rcvr).Type().Name()
   if useName {
      sname = name
   }
   if sname == "" {
      s := "rpc.Register: no service name for type " + s.typ.String()
      log.Print(s)
      return errors.New(s)
   }
   if !isExported(sname) && !useName {
      s := "rpc.Register: type " + sname + " is not exported"
      log.Print(s)
      return errors.New(s)
   }
   s.name = sname

   // Install the methods
   s.method = suitableMethods(s.typ, true)

   if len(s.method) == 0 {
      str := ""

      // To help the user, see if a pointer receiver would work.
      method := suitableMethods(reflect.PtrTo(s.typ), false)
      if len(method) != 0 {
         str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
      } else {
         str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
      }
      log.Print(str)
      return errors.New(str)
   }

   if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
      return errors.New("rpc: service already defined: " + sname)
   }
   return nil
}

根据需要被远程调用的类,通过反射得到被注册的类的类型,如果没有定义所要被映射的名字,那么直接使用类名,这个名字首字母需要大写来作为暴露的类。

关于方法的遍历,将在suitableMethods()方法中处理。

func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
   methods := make(map[string]*methodType)
   for m := 0; m < typ.NumMethod(); m++ {
      method := typ.Method(m)
      mtype := method.Type
      mname := method.Name
      // Method must be exported.
      if method.PkgPath != "" {
         continue
      }
      // Method needs three ins: receiver, *args, *reply.
      if mtype.NumIn() != 3 {
         if reportErr {
            log.Printf("rpc.Register: method %q has %d input parameters; needs exactly three\n", mname, mtype.NumIn())
         }
         continue
      }
      // First arg need not be a pointer.
      argType := mtype.In(1)
      if !isExportedOrBuiltinType(argType) {
         if reportErr {
            log.Printf("rpc.Register: argument type of method %q is not exported: %q\n", mname, argType)
         }
         continue
      }
      // Second arg must be a pointer.
      replyType := mtype.In(2)
      if replyType.Kind() != reflect.Ptr {
         if reportErr {
            log.Printf("rpc.Register: reply type of method %q is not a pointer: %q\n", mname, replyType)
         }
         continue
      }
      // Reply type must be exported.
      if !isExportedOrBuiltinType(replyType) {
         if reportErr {
            log.Printf("rpc.Register: reply type of method %q is not exported: %q\n", mname, replyType)
         }
         continue
      }
      // Method needs one out.
      if mtype.NumOut() != 1 {
         if reportErr {
            log.Printf("rpc.Register: method %q has %d output parameters; needs exactly one\n", mname, mtype.NumOut())
         }
         continue
      }
      // The return type of the method must be error.
      if returnType := mtype.Out(0); returnType != typeOfError {
         if reportErr {
            log.Printf("rpc.Register: return type of method %q is %q, must be error\n", mname, returnType)
         }
         continue
      }
      methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
   }
   return methods
}

 

遍历所需要被远程调用的类所暴露的方法,这里的方法必须满足含有三个参数,第二个参数,也就是调用的参数,不能是个指针,而作为返回值的第三个参数,则必须是个指针。同时在返回值上,必须有一个返回值,并且是异常类型的返回值。

 

 

完成上述方法的对于被调用方法 的格式检查,将得到的用来映射的名字与得到的service进行作为键值对存放在map中,表明已经完成对于被调用的方法的注册。

 

在接收到相应的http请求的时候,rpc包中的server同样作为http的handler,实现了ServerHttp()方法来处理远程调用的相应。

对于所要调用的类的选择,在readRequestheader()方法中选择。

func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, err error) {
   // Grab the request header.
   req = server.getRequest()
   err = codec.ReadRequestHeader(req)
   if err != nil {
      req = nil
      if err == io.EOF || err == io.ErrUnexpectedEOF {
         return
      }
      err = errors.New("rpc: server cannot decode request: " + err.Error())
      return
   }

   // We read the header successfully. If we see an error now,
   // we can still recover and move on to the next request.
   keepReading = true

   dot := strings.LastIndex(req.ServiceMethod, ".")
   if dot < 0 {
      err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
      return
   }
   serviceName := req.ServiceMethod[:dot]
   methodName := req.ServiceMethod[dot+1:]

   // Look up the request.
   svci, ok := server.serviceMap.Load(serviceName)
   if !ok {
      err = errors.New("rpc: can't find service " + req.ServiceMethod)
      return
   }
   svc = svci.(*service)
   mtype = svc.method[methodName]
   if mtype == nil {
      err = errors.New("rpc: can't find method " + req.ServiceMethod)
   }
   return
}

 

这里,通过解析得到request中包含的所要调用的相应类的方法,由于类和方法之间通过’.’相互分割,通过’.’前后的数据得到被调用的类与方法。

 

通过类名在map中取得相应的service,并根据方法名从service取得相应的方法。

在得到了所调用的类以及方法之后,在那么就可以调用被远程调用的方法。在异步或者同步的情况下,通过反射机制,使用call()方法调用相关的方法。

func (s *service) call(server *Server, sending *sync.Mutex, wg *sync.WaitGroup, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) {
   if wg != nil {
      defer wg.Done()
   }
   mtype.Lock()
   mtype.numCalls++
   mtype.Unlock()
   function := mtype.method.Func
   // Invoke the method, providing a new value for the reply.
   returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
   // The return value for the method is an error.
   errInter := returnValues[0].Interface()
   errmsg := ""
   if errInter != nil {
      errmsg = errInter.(error).Error()
   }
   server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
   server.freeRequest(req)
}

 

这里反射机制的执行的前提在于之前方法定义注册的时候对于返回值和参数个数以及类型 的严格检查。并在调用之后,将返回结果的参数作为成员在返回response的时候一并加入,在方法结束的时候,如果存在wg锁则解锁,表明一次连接的完成。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值