01
RPC 是什么?
RPC 是远程过程调用(Remote Procedure Call),用于调用方和被调用方两个进程间的交互,并且提供类似本地方法调用的形式。RPC 广泛用于在分布式系统中不同节点间的通信。
02
Go 语言 RPC 标准库
在 Go 语言的标准库中,也提供了一个简单的 RPC 实现(net/rpc)。rpc 包提供对对象在网络或其他 I/O 连接中导出方法的访问。服务器端注册对象,使其作为可见服务,服务的名称是对象类型名称。注册后,对象的导出方法将可远程访问。服务器可以注册不同类型的多个对象(服务),但注册同一类型的多个对象是错误的。
对象的导出方法有以下几点要求:
- 方法的类型是可导出的。
- 方法是可导出的。
- 方法有两个参数,都是可导出类型或内置类型。
- 方法的第二个参数是指针。
- 方法返回一个错误类型。
实际上,方法看起来像这样:
func (t *T) MethodName(argType T1, replyType *T2) error
其中 T1 和 T2 可以通过 encoding/gob 编码进行序列化。即使使用不同的编码解码器,这些限制也适用。将来,对自定义的编码解码器的限制可能会宽松一些。
该方法的第一个参数表示调用方提供的参数;第二个参数表示要返回给调用方的结果参数。方法的返回值(如果不是 nil)作为字符串传递回来,客户端认为该字符串就像由 errors.New 创建的错误一样。如果返回错误,则不会将回复参数发送回客户端。
服务器端可以调用 ServeConn 处理单个连接上的请求。更典型的是,它将创建一个网络监听器并调用 Accept,或者,对于 HTTP 监听器,调用 HandleHTTP 和 http.Serve。
想要使用该服务的客户端会建立连接,然后在连接上调用 NewClient。
更方便的函数是 Dial (DialHTTP) ,会在原始网络连接(HTTP 连接)依次执行这两个步骤。生成的 Client 对象有两个方法,即 Call 和 Go,它们的参数是要调用的服务和方法,一个包含参数的指针,一个用于接收结果的指针。
Call 方法等待远程调用完成。Go 方法异步发送调用请求,并使用返回的 Call结构体类型的 "Done 通道" 传递完成的信号。
除非显式设置了编码解码器,否则 net/rpc 包默认采用 encoding/gob 包编码解码数据。
03
RPC 怎么使用?
通过一个简单的示例,我们演示 Go 语言标准库 net/rpc 的使用方法。
RPC 方法:
服务器端定义一个可导出的 User 类型和一个符合 RPC 方法定义要求的 GetUser 方法:
type User struct { ID int Name string } // rpc 方法 func (u *User) GetUser(id int, user *User) error { userMap := map[int]User{ 1: {ID: 1, Name: "frank"}, 2: {ID: 2, Name: "lucy"}, } if userInfo, ok := userMap[id]; ok { *user = userInfo } return nil }
服务器端:
服务器端被调用(用于 HTTP 服务):
func main() { _ = rpc.Register(new(message.User)) rpc.HandleHTTP() listener, _ := net.Listen("tcp", ":8081") _ = http.Serve(listener, nil) }
客户端:
此时,客户端可以看到具有 "User.GetUser" 方法的服务 "User"。要调用方法,客户端首先呼叫服务器端:
client, _ := rpc.DialHTTP("tcp", ":8081")
然后客户端可以进行远程调用:
Call 方法,同步调用:
id := 1 var user message.User _ = client.Call("User.GetUser", id, &user) fmt.Println(user)
Go 方法,异步调用:
userCall := client.Go("User.GetUser", id, &user, nil) if replyCall := <-userCall.Done; replyCall != nil { fmt.Println(user) }
服务器端的实现通常为客户端提供简单、类型安全的包装。
net/rpc 包已冻结,不接受新功能。
04
总结
本文简要描述 Go 语言标准库 net/rpc 包的使用方法,通过阅读本文,读者应该已经对Go 语言标准库 net/rpc 有了初步的认识。