Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——服务端处理RPC请求的原理及源代码分析

远程过程调用 RPC——服务端处理RPC请求的原理及源代码分析

RPC网络调用会使用到RequestResponse两个结构体,分别是请求参数和返回参数,通过编解码器(gob/json)实现二进制和结构体的互相转换,它们的定义如下所示:

  在Go语言的RPC服务端,一个持续运行的循环不断地监听并接收来自客户端的RPC请求。每当一个请求到达时,服务端会将其转交给ReadRequestHandler进行处理。这个处理程序是RPC框架中的关键组件,它负责解析请求并准备相应的响应。

  在处理请求的过程中,ReadRequestHandler首先会从服务端之前通过Register方法保存的方法映射表中检索出客户端请求调用的具体方法。这个映射表实际上是一个保存了服务名、方法名与对应实现函数之间关系的字典(map)。

  一旦找到了对应的方法,处理程输中的二进制数据转换为Go语言中可以理解的数据结构。

  接下来,利用Go语言的反射机制,ReadRequestHandler能够动态地调用解码后的参数所对应的方法。这种动态调用的能力是RPC框架灵活性的关键,它允许服务端在运行时决定执行哪个具体的函数,而无需在编译时确定。

 方法执行完成后,处理程序会将返回的结果编码成RPC响应消息。这个过程与解码相反,它将Go语言中的数据结构转换回适合网络传输的格式。

  最后,编码后的响应消息会被发送回客户端,完成了整个RPC调用的处理流程。通过这种方式,Go语言的RPC服务端能够高效地处理来自不同客户端的各种请求,提供了强大的服务能力和灵活性。

一、接收请求

  下面,我们来看一下具体的代码实现。首先是Accept函数,它会无限循环的调用net.ListenerAccept 函数来获取客户端建立连接的请求,获取到连接请求后,会使用协程来处理请求,代码如下:

 ServeConn函数会从建立的连接中读取数据,然后创建一个 gobServerCodec,并将其交由ServerServeCode函数处理,如下所示:

二、读取并解析请求数据

ServeCodec函数会循环地调用readRequest函数读取网络连接上的字节流,解析出请求,然后开启协程执行Servercall函数,处理对应的RPC调用:

readRequest函数会调用readRequestHeader来获取RPC的一些头部信息,然后再解析消息体中携带的参数,最后初始化响应的返回值类型。这部分代码太长了,直接贴源码如下:

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
// 此函数是Server结构体的一个方法,用于从编解码器(codec)中读取RPC请求, 
// 并解析出服务、方法类型、请求对象、参数和回复的反射值等信息。 
func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, err error) { 
   
 // 首先调用readRequestHeader方法读取请求头,获取服务、方法类型、请求对象和是否继续读取的标志等信息。 
 service, mtype, req, keepReading, err = server.readRequestHeader(codec) 
   
 // 如果在读取请求头时发生错误,进入错误处理流程。 
 if err != nil { 
 // 如果keepReading为false,表示不需要继续读取后续数据,直接返回。 
 if !keepReading { 
 return 
 
   
 // 如果需要继续读取但发生了错误,则丢弃请求体,并返回。 
 // 这样做可能是为了保持与客户端的通信协议同步,即使发生错误也继续读取完整个请求。 
 codec.ReadRequestBody(nil) 
 return 
 
   
 // 解码参数值。根据方法类型的参数类型,创建一个新的反射值来存储参数。 
 argIsValue := false // 标记参数是否是一个值类型,如果是,则在调用前需要进行间接引用。 
   
 // 检查方法类型的参数是否是指针类型。 
 if mtype.ArgType.Kind() == reflect.Pointer { 
 // 如果是指针类型,则创建一个该指针类型指向的新实例。 
 argv = reflect.New(mtype.ArgType.Elem()) 
 } else
 // 如果不是指针类型,则直接创建一个该类型的新实例,并标记参数是一个值类型。 
 argv = reflect.New(mtype.ArgType) 
 argIsValue = true 
 
   
 // 此时argv一定是一个指针类型的反射值。 
 // 调用编解码器的ReadRequestBody方法读取请求体,并将数据填充到之前创建的参数反射值中。 
 if err = codec.ReadRequestBody(argv.Interface()); err != nil { 
 // 如果在读取请求体时发生错误,直接返回。 
 return 
 
   
 // 如果参数是一个值类型,则通过Elem()方法获取其指向的实际值,以便后续使用。 
 if argIsValue { 
 argv = argv.Elem() 
 
   
 // 创建一个新的反射值来存储回复数据。回复数据的类型总是指针类型,指向方法类型的回复类型。 
 replyv = reflect.New(mtype.ReplyType.Elem()) 
   
 // 根据回复类型的元素类型,进行初始化。如果是Map或Slice类型,则创建一个空的Map或Slice。 
 switch mtype.ReplyType.Elem().Kind() { 
 case reflect.Map: 
 // 如果是Map类型,则创建一个空的Map。 
 replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem())) 
 case reflect.Slice: 
 // 如果是Slice类型,则创建一个空的Slice。 
 replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0)) 
 
   
 // 返回解析得到的服务、方法类型、请求对象、参数和回复的反射值、是否继续读取的标志以及可能的错误信息。 
 return 
}

readRequestHeader函数是解析RPC请求的关键函数,它会首先解析请求的头部信息,然后获取信息中包含RPC请求的struct名字和方法名字,然后从Servermap中获取到服务端注册的service及其对应方法,这部分代码也太长了,直接贴源码如下:

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
65
66
67
68
69
70
71
72
// 该函数是Server结构体的一个方法,用于从编解码器(codec)中读取RPC请求的头信息。
func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, err error) {
    
 // 调用server的getRequest方法来获取一个新的请求对象。
 // 这通常是为了复用请求对象,减少内存分配和垃圾回收的开销。
 req = server.getRequest()
    
 // 调用codec的ReadRequestHeader方法来从编解码器中读取请求头,
 // 并将读取到的信息填充到之前获取的req对象中。
 err = codec.ReadRequestHeader(req)
    
 // 如果在读取请求头时发生错误,进入错误处理流程。
 if err != nil {
 // 防御性编程:如果读取失败,将请求对象设置为nil,
 // 防止后续代码错误地使用可能包含不完整或错误数据的请求对象。
 req = nil
    
 // 检查错误是否为io.EOF或io.ErrUnexpectedEOF,这两种错误表示输入流结束,
 // 可能是因为客户端关闭了连接或数据不完整。
 if err == io.EOF || err == io.ErrUnexpectedEOF {
 // 如果是这两种错误之一,则直接返回,不再处理。
 return
 }
    
 // 如果不是上述两种特定错误,则创建一个新的错误消息,
 // 该消息包含原始错误信息,但前缀有额外的描述信息,
 // 以便于调用者更容易理解错误的来源和性质。
 err = errors.New("rpc: server cannot decode request: " + err.Error())
 return
 }
    
 // 如果成功读取了请求头,则设置keepReading标志为true,
 // 表示可以继续读取后续的请求数据(如果有的话)。
 keepReading = true
    
 // 使用strings.LastIndex查找服务名和方法名之间的分隔符(这里是点号'.'),
 // 以便将服务名和方法名分割开。
 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:]
    
 // 使用服务名在server的服务映射表(serviceMap)中查找对应的服务。
 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
}

三、执行远程方法并返回响应

Servercall函数就是通过mtype.method.Func.Call()反射调用对应RPC过程的方法,其源代码如下:

它还会调用sendResponse将返回值发送给RPC客户端,源代码如下:

posted @   左扬  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
levels of contents
点击右上角即可分享
微信分享提示