geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

动手写RPC框架 - GeeRPC第三天 服务注册(service register) | 极客兔兔 #93

Open geektutu opened 3 years ago

geektutu commented 3 years ago

https://geektutu.com/post/geerpc-day3.html

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第三天实现了服务注册,即将 Go 语言结构体通过反射映射为服务。

IcePigZDB commented 3 years ago

感谢博主的详细博客 在用wg举例的main中returns的容量好像应该是method.Type.NumOut()


        argv := make([]string, 0, method.Type.NumIn())
        returns := make([]string, 0, method.Type.NumIn())
        // j 从 1 开始,第 0 个入参是 wg 自己。
        for j := 1; j < method.Type.NumIn(); j++ {
            argv = append(argv, method.Type.In(j).Name())
        }
        for j := 0; j < method.Type.NumOut(); j++ {
            returns = append(returns, method.Type.Out(j).Name())
        }
geektutu commented 3 years ago

@IcePigZDB 感谢指出问题,已经修复~

limaoxiaoer commented 3 years ago

returnValues := f.Call([]reflect.Value{s.rcvr, argv, replyv})

call的参数中为什么要加 s.rcvr?

这个函数不是只需要Sum(args Args, reply *int)两个参数么

geektutu commented 3 years ago

@limaoxiaoer 第 0 个参数是对象自己,正常调用是 A.func(argv1, argv2),反射的时候就是 Call(A, argv1, argv2)

Howie59 commented 3 years ago

想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

geektutu commented 3 years ago

@Howie59 newService 之后解析出结构体的名称,也就是 serviceMap 中的 key。即使有参数变化,只要 key 不变,serviceMap 也是不会更新的。这么实现是为了简单,判断 serviceMap 是否有,也得通过反射才能得到。Register 调用次数理论上很少,所以这一块没考虑性能优化。

liyuxuan89 commented 3 years ago

func (m *methodType) newArgv() reflect.Value { var argv reflect.Value // arg may be a pointer type, or a value type if m.ArgType.Kind() == reflect.Ptr { argv = reflect.New(m.ArgType.Elem()) } else { argv = reflect.New(m.ArgType).Elem() } return argv } 这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

shawn0762 commented 3 years ago

NewService方法里面: s.rcvr = reflect.ValueOf(rcvr) s.name = reflect.Indirect(s.rcvr).Type().Name() s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

devhg commented 3 years ago

问下 rcvr是哪些单词的简写

shawn0762 commented 3 years ago

@devhg 问下 rcvr是哪些单词的简写

receiver

shengxiang19 commented 3 years ago

@shawn0762 NewService方法里面: s.rcvr = reflect.ValueOf(rcvr) s.name = reflect.Indirect(s.rcvr).Type().Name() s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

因为rcvr可能是一个指针,通过Indirect可以返回它指向的对象的类型。不然的话,它的type就是reflect.Ptr。

chenshuidejidan commented 2 years ago
// make sure that argvi is a pointer, ReadBody need a pointer as parameter
    argvi := req.argv.Interface()
    if req.argv.Type().Kind() != reflect.Ptr {
        argvi = req.argv.Addr().Interface()
    }

这里为什么是 !=reflect.Ptr 呢?不该是等于指针类型的时候取addr,拿到指针类型的interface?

lwb0214 commented 2 years ago

@chenshuidejidan

// make sure that argvi is a pointer, ReadBody need a pointer as parameter
  argvi := req.argv.Interface()
  if req.argv.Type().Kind() != reflect.Ptr {
      argvi = req.argv.Addr().Interface()
  }

这里为什么是 !=reflect.Ptr 呢?不该是等于指针类型的时候取addr,拿到指针类型的interface?

Addr returns a pointer value representing the address of v.(官方文档)

raykn commented 2 years ago

@Howie59 想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该也不需要判断吧,即便存在也是要覆盖,不存在则是新增,验证是不是正确的Method又是一定要走的逻辑,所以判断存不存在都可以

zwkdhm commented 2 years ago

@liyuxuan89 func (m *methodType) newArgv() reflect.Value { var argv reflect.Value // arg may be a pointer type, or a value type if m.ArgType.Kind() == reflect.Ptr { argv = reflect.New(m.ArgType.Elem()) } else { argv = reflect.New(m.ArgType).Elem() } return argv } 这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

好像确实会,而且加了之后可以set成功,但是f.call会panic,说参数类型不对 不加Elem()的话如果是指针型参数,我在测试用例中改成 argv.Elem().Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 就成功了

WeiguoEric commented 2 years ago
func (m *methodType) newReplyv() reflect.Value {
    // reply must be a pointer type
    replyv := reflect.New(m.ReplyType.Elem())
    switch m.ReplyType.Elem().Kind() {
    case reflect.Map:
        replyv.Elem().Set(reflect.MakeMap(m.ReplyType.Elem()))
    case reflect.Slice:
        replyv.Elem().Set(reflect.MakeSlice(m.ReplyType.Elem(), 0, 0))
    }
    return replyv
}

这里为什么要区分reflect.Map和reflect.Slices?

qiu-peng20 commented 2 years ago
    argvi := req.argv.Interface()
    if req.argv.Type().Kind() != reflect.Ptr {
        argvi = req.argv.Addr().Interface()
    }

    if err = cc.ReadBody(argvi); err != nil {
        log.Println("rpc server : read argv err", err)
        return req, err
    }

我的这一步报错了:read argv err gob: type mismatch in decoder: want struct type main.Args; got non-struct

不知道为啥

qiu-peng20 commented 2 years ago

@zwkdhm

@liyuxuan89 func (m *methodType) newArgv() reflect.Value { var argv reflect.Value // arg may be a pointer type, or a value type if m.ArgType.Kind() == reflect.Ptr { argv = reflect.New(m.ArgType.Elem()) } else { argv = reflect.New(m.ArgType).Elem() } return argv } 这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

好像确实会,而且加了之后可以set成功,但是f.call会panic,说参数类型不对 不加Elem()的话如果是指针型参数,我在测试用例中改成 argv.Elem().Set(reflect.ValueOf(Args{Num1: 1, Num2: 3})) 就成功了

你这个地方的参数类型不对是怎么解的

cyj19 commented 2 years ago

@Howie59 想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该是参考了标准库rpc包的源码的写法

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 _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
        return errors.New("rpc: service already defined: " + sname)
    }
    return nil
}
Roderland commented 2 years ago

在用wg举例的main中打印参数类型method.Type.In(j).Name()如果是参数是interface{}或chan这种会返回空字符串,用method.Type.In(j).String()会不会好一点

func main() {
    var wg sync.WaitGroup
    typ := reflect.TypeOf(&wg)
    for i := 0; i < typ.NumMethod(); i++ {
        method := typ.Method(i)
        argv := make([]string, 0, method.Type.NumIn())
        returns := make([]string, 0, method.Type.NumOut())
        // j 从 1 开始,第 0 个入参是 wg 自己。
        for j := 1; j < method.Type.NumIn(); j++ {
            argv = append(argv, method.Type.In(j).Name())
        }
        for j := 0; j < method.Type.NumOut(); j++ {
            returns = append(returns, method.Type.Out(j).Name())
        }
        log.Printf("func (w *%s) %s(%s) %s",
            typ.Elem().Name(),
            method.Name,
            strings.Join(argv, ","),
            strings.Join(returns, ","))
    }
}
609412635 commented 2 years ago

@liyuxuan89 func (m *methodType) newArgv() reflect.Value { var argv reflect.Value // arg may be a pointer type, or a value type if m.ArgType.Kind() == reflect.Ptr { argv = reflect.New(m.ArgType.Elem()) } else { argv = reflect.New(m.ArgType).Elem() } return argv } 这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

我自己测试了下确实会出现,可以用canSet()判断返回了是false ,不加Elem(),就会不可寻址,赋值失败

eurus10 commented 2 years ago

我想请教一下为什么Server注册服务的时候要用sync.Map存储呢 RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

vvzhihao commented 1 year ago

@eurus10 我想请教一下为什么Server注册服务的时候要用sync.Map存储呢 RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

我的观点是:map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。 要是注册新的服务又同时读取用map会发生错误

niconical commented 1 year ago

@WeiguoEric

func (m *methodType) newReplyv() reflect.Value {
  // reply must be a pointer type
  replyv := reflect.New(m.ReplyType.Elem())
  switch m.ReplyType.Elem().Kind() {
  case reflect.Map:
      replyv.Elem().Set(reflect.MakeMap(m.ReplyType.Elem()))
  case reflect.Slice:
      replyv.Elem().Set(reflect.MakeSlice(m.ReplyType.Elem(), 0, 0))
  }
  return replyv
}

这里为什么要区分reflect.Map和reflect.Slices?

个人理解是给map和slice初始化

niconical commented 1 year ago

@shawn0762 NewService方法里面: s.rcvr = reflect.ValueOf(rcvr) s.name = reflect.Indirect(s.rcvr).Type().Name() s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

s.name执行在先

ShiMaRing commented 1 year ago

@niconical

@shawn0762 NewService方法里面: s.rcvr = reflect.ValueOf(rcvr) s.name = reflect.Indirect(s.rcvr).Type().Name() s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

原因在于无法确定用户传入的s.rcvr类型为结构体还是为指针,如果用户传入的为指针的话,直接采用s.typ.Name()输出的为空字符串

log.Println(" struct name: " + reflect.TypeOf(foo).Name())//输出Foo

log.Println("pointer name: " + reflect.TypeOf(&foo).Name())//输出空串

因此需要采用reflect.Indirect(s.rcvr)方法,提取实例对象再获取名称

huty1998 commented 1 year ago

太强了

SCUTking commented 7 months ago

这个应该只是一个单机版的服务注册

SCUTking commented 7 months ago

@vvzhihao

@eurus10 我想请教一下为什么Server注册服务的时候要用sync.Map存储呢 RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

我的观点是:map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。 要是注册新的服务又同时读取用map会发生错误

咋在程序运行时注册新的服务呢?

SCUTking commented 7 months ago

@cyj19

@Howie59 想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该是参考了标准库rpc包的源码的写法

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 _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
      return errors.New("rpc: service already defined: " + sname)
  }
  return nil
}

因为要获取服务的名称去map映射获取就得获取一个service实例,dup就是用于判断服务之前是否存在的