脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Golang原生rpc(rpc服务端源码解读)

Golang原生rpc(rpc服务端源码解读)

2022-09-13 11:07鹿灏楷silves Golang

本文主要介绍了Golang原生rpc(rpc服务端源码解读),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

创建rpc接口,需要几个条件

  • 方法的类型是可输出的
  • 方法的本身也是可输出的
  • 方法必须有两个参数,必须是输出类型或者是内建类型
  • 方法的第二个参数是指针类型
  • 方法返回的类型为error

rpc服务原理分析

server端

  • 服务注册
  • 处理网络调用

服务注册 通过反射处理,将接口存入到map中,进行调用 注册服务两个方法

?
1
2
3
func Register (rcvr interface{}) error {}
func RegisterName (rcvr interface{} , name string) error {}
//指定注册的名称

注册方法的源代码解读 首先,无论是Register还是RegisterName底层代码都是调用register方法,进行服务注册。 server.go register方法解读

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func (server *Server) register(rcvr interface{}, name string, useName bool) error {
    //创建一个service实例
    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)
    }
    //判断方法名是否暴漏的,如果方法名不是暴露的,则会导致调用不成功,所以返回false
    if !token.IsExported(sname) && !useName {
        s := "rpc.Register: type " + sname + " is not exported"
        log.Print(s)
        return errors.New(s)
    }
    s.name = sname
 
    // Install the methods
    //调用suitableMethods函数,进行返回接口,在suitableMethods中判断方法是否符合作为rpc接口的条件,如果符合,则进行添加到services中
    s.method = suitableMethods(s.typ, true)
 
    if len(s.method) == 0 {
        str := ""
 
        // To help the user, see if a pointer receiver would work.
        //如果方法绑定到结构体的地址上,使用reflect.TypeOf()是不会发现方法的,所以也要进行查找绑定到结构体地址上的方法
        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
}

suitableMethod方法解读

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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.
        //如果传入的参数,不为三个,则会报错,这里为什么是三个?
        //golang方法体中默认传入结构体实例,所以request,*response,结构体实例一共三个参数
        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.
        //判断第二个参数是否为指针,如果不为指针,则返回false。
        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.
        //返回结果是否为一个值,且为error
        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
        }
        //将接口加入service
        methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
    }
    return methods
}

接收到请求后会不断的解析请求 解析请求的两个方法 readRequestHeader

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
}

readRequest方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, err error) {
    service, mtype, req, keepReading, err = server.readRequestHeader(codec)
//调用上面的readRequestHeader方法,进行解码,并返返回接口数据
    if err != nil {
        if !keepReading {
            return
        }
        // discard body
        codec.ReadRequestBody(nil)
        return
    }
 
    // Decode the argument value.
    argIsValue := false // if true, need to indirect before calling.
    //判断传擦是否为指针,如果为指针,需要使用Elem()方法,进行指向结构体
    if mtype.ArgType.Kind() == reflect.Ptr {
        argv = reflect.New(mtype.ArgType.Elem())
    } else {
        argv = reflect.New(mtype.ArgType)
        argIsValue = true
    }
    // argv guaranteed to be a pointer now.
    if err = codec.ReadRequestBody(argv.Interface()); err != nil {
        return
    }
    if argIsValue {
        argv = argv.Elem()
    }
 
    replyv = reflect.New(mtype.ReplyType.Elem())
 
    switch mtype.ReplyType.Elem().Kind() {
    case reflect.Map:
        replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem()))
    case reflect.Slice:
        replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0))
    }
    return
}

call方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.
    //调用call方法,并将参数转化为valueof型参数,
    returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
    // The return value for the method is an error.
    //将返回的error进行读取,转化为interface{}型
    errInter := returnValues[0].Interface()
    errmsg := ""
    if errInter != nil {
    //将error进行断言
        errmsg = errInter.(error).Error()
    }
    server.sendResponse(sending, req, replyv.Interface(), codec, errmsg)
    server.freeRequest(req)
}

注册的大概流程

  • 根据反射,进行接口的获取
  • 使用方法判断接口是否符合作为rpc接口的规范(有两个参数,第二个参数为指针,返回一个参数error)
  • 如果不符合规范,将返回error,符合规范,将存入map,进行提供调用

接收请求的大概流程

  • 首先,不断的接收数据流,并进行解码,解码之后为data.data,所以我们需要使用 . 作为分隔符,进行数据的截切和读取
  • 将读取的数据在注册的map中进行查找,如果查找到,返回相关的service和其他数据
  • 进行调用

到此这篇关于Golang原生rpc(rpc服务端源码解读)的文章就介绍到这了,更多相关Golang原生rpc内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7083083938603728903

延伸 · 阅读

精彩推荐
  • GolangGo语言应该什么情况使用指针

    Go语言应该什么情况使用指针

    go语言的指针类型和C/C++的指针类型用法是一样的,那么Go语言应该什么情况使用指针,本文就详细的介绍一下,感兴趣的可以了解一下...

    微客鸟窝11402021-08-17
  • Golanglogrus日志自定义格式操作

    logrus日志自定义格式操作

    这篇文章主要介绍了logrus日志自定义格式操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    chen0912276311242021-02-02
  • GolangGo 语言实现安全计数的若干种方法

    Go 语言实现安全计数的若干种方法

    我正研究共享计数器的简单经典实现,实现方式使用的是 C++ 中的互斥锁,这时,我非常想知道还有哪些线程安全的实现方式。我通常使用 Go 来满足自己的...

    Golang来啦6752021-07-26
  • Golanggolang time包做时间转换操作

    golang time包做时间转换操作

    这篇文章主要介绍了golang time包做时间转换操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    weixin_415714496312021-03-12
  • Golanggo语言实现顺序存储的栈

    go语言实现顺序存储的栈

    这篇文章主要介绍了go语言实现顺序存储的栈,实例分析了Go语言实现顺序存储的栈的原理与各种常见的操作技巧,需要的朋友可以参考下 ...

    OSC首席键客2552020-04-22
  • Golang从源码的角度看Go语言Flag库如何解析命令行参数!

    从源码的角度看Go语言Flag库如何解析命令行参数!

    Parse的代码里用到了一个,CommandLine共享变量,这就是内部库维护的FlagSet,所有的参数都会插到里面的变量地址向地址的指向赋值绑定。...

    机智的程序员小熊10662021-08-11
  • Golang解决Goland 提示 Unresolved reference 错误的问题

    解决Goland 提示 Unresolved reference 错误的问题

    这篇文章主要介绍了解决Goland 提示 Unresolved reference 错误的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    神神的蜗牛17252021-02-26
  • Golanggo语言中切片与内存复制 memcpy 的实现操作

    go语言中切片与内存复制 memcpy 的实现操作

    这篇文章主要介绍了go语言中切片与内存复制 memcpy 的实现操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    许野平7292021-06-01