RPC 叫做远程过程调用,有的时候也叫做远程方法或者远程函数调用
请求和响应的话无非就是请求的数据,数据的话主要有编码和解码,当服务器只提供一个功能的时候,那我们只要把一个功能的数据交给他即可。
但是如何服务器提供了多个功能的时候,比如 ls、get、put、delete
这些功能的时候,那我需要在传递的数据中还要包含我们的操作。而我们在调用这些功能的时候其实就类似于远程的方法调用,因为这个本身就是在调用服务器端的方法和函数上。
RPC 即远程过程调用(Remote Procedure Call),用于构建计算机之间的通信协议,该协议允许运行于一台计算机的程序调用另一台计算机上的程序,开发人员无需对交互过程进行编程
-
rpc 和 rpc/jsonrpc 包提供了对 RPC 的支持。
-
rpc 构建于 TCP 或 HTTP 协议之上,底层数据编码使用 gob,因 gob 编码为 golang 自定义,所以无法支持跨语言调用;
-
rpc/jsonrpc 构建于 TCP 协议之上,底层数据编码使用 json,可支持跨语言调用
-
2 RPC 包
在 go 中提供了 RPC 包。
-
服务器端提供的功能
-
编解码
-
监听服务/接受请求
-
函数调用
-
响应
-
-
客户端提供的功能
-
连接服务器
-
发送请求
-
处理结果
-
rpc 使用过程中,就像调用本地方法一样去调用远程方法。
rpc 没有限制底层协议,所以我们可以通过 udp/tcp 或者 http 协议来做。因为 http 也是基于在 tcp 之上的
go 里面的 rpc 其实提供了两种:
-
rpc :协议可以使用 tcp/http,编码实现的是 gob 编码
-
jsonrpc:协议建立在 tcp ,编码实现的是 json 编码
net/rpc 包
-
常用函数
-
Register:注册 RPC 服务
-
RegisterName:注册 RPC 服务,并设置服务名称
-
HandleHTTP: 使用 HTTP 构建的 RPC 服务
-
ServeConn:处理客户端连接
-
Accept:监听客户端连接并调用 ServeConn 进行处理
-
常用结构体
-
Server:RPC 服务端
-
常用函数
➢ NewServer:创建 RPC 服务端
-
常用方法
➢ Register:注册 RPC 服务
➢ RegisterName:注册 RPC 服务,并设置服务名称
➢ HandleHTTP:使用 HTTP 构建的 RPC 服务
➢ ServeConn:处理客户端连接
➢ Accept:监听客户端连接并调用 ServeConn 进行处理
-
-
Client:RPC 客户端
-
常用函数
➢ Dial:连接使用 TCP 构建的 RPC 服务
➢ DialHTTP:连接使用 HTTP 构建的 RPC 服务
➢ NewClient:创建 RPC 客户端
-
常用方法
➢ Call:同步调用 RPC 服务
➢ Go:异步调用 RPC 服务
➢ Close:关闭客户端连接
-
3 TCP RPC 范例
在 rpc 中由于传递的是数据,所以他有一个编解码的过程,也就是我们需要提供一个编码和解码在 go 里面的对象。
编码和解码是将一个东西转换为另一个东西,肯定是把我协议请求中的数据,和 go 程序中的对象进行转换了
所以我们就要定义这个对象,这个对象分为两种,也就是当我们请求的时候请求的对象,响应的时候响应的对象。
也就是说我们自己定义 RPC 的话就需要自己定义请求对象和响应对象,还有他的多个操作函数,因为这种函数也就是对应的各种远程调用操作。那么我就可以明白一点,至少请求就是方法或者说函数的输入,响应呢就是方法或者函数的返回值或者说参数
3.1 rpc 服务端开发
server 编解码需要使用到 响应对象 和 请求对象,一般我们会把编解码的过程放到公共的包中,因为客户端和服务器端都需要使用
package main
import (
"fmt"
"net"
"net/rpc"
)
// 远程服务 Add(1,2) int 最后返回 1 + 2 的和
// 1.定义 AddRequest 请求结构体,并且属性首字母一定要大写,因为要提供给 rpc 包进行访问,设置为公开
type AddRequest struct {
Left int
Right int
}
// 2.定义 AddResponse 响应信息结构体,并且属性首字母一定要大写,因为要提供给 rpc 包进行访问,设置为公开
type AddResponse struct {
Result int
}
// 3.定义 Calc 计算器结构体
type Calc struct{}
// 4.定义 Add 方法,
// 参数 1 req:请求对象(可以是指针/值)
// 参数 2 resq:响应对象(是指针)
// 在 rpc 方法中 返回值 必须是 error
func (c *Calc) Add(req AddRequest, resq *AddResponse) error {
fmt.Println("calc.Add 启动")
// 将 AddRequest 结构体两个属性相加,赋值给 AddResponse 的 resq.result 属性
resq.Result = req.Left + req.Right
return nil
}
type AAA struct{}
func (a *AAA) Add(req AddRequest, resq *AddResponse) error {
fmt.Println("AAA.a 方法")
return nil
}
func main() {
// 注册,也就是告诉 rpc 我需要传递那个函数给客户端
// 这里传递 &Calc{} 结构体
rpc.Register(&Calc{})
// 这里传递 &AAA{} 结构体
rpc.Register(&AAA{})
// 开启 rpc 之前需要先启动监听
addr := ":8888"
network := "tcp"
listens, _ := net.Listen(network, addr)
// 启动 rpc
// rpc.Accept() 能接收来自 net.Listener 的链接,能够处理数据,并且自动实现了循环结束
rpc.Accept(listens)
listens.Close()
}
启动服务端
[08:01:28 root@go tcprpc]#go run server.go
3.2 rpc 客户端开发
client 编解码需要使用到 响应对象 和 请求对,一般我们会把编解码的过程放到公共的包中,因为客户端和服务器端都需要使用。
func rpc.Dial(network string, address string) (*rpc.Client, error)
func (*rpc.Client).Call(serviceMethod string, args interface{}, reply interface{}) error
// serviceMethod:指定调用服务器端方法名称
// args:请求信息
// reply:响应信息
package main
import (
"fmt"
"net/rpc"
)
// 1.定义 AddRequest 请求结构体
type AddRequest struct {
Left int
Right int
}
// 2.定义 AddResponse 响应信息结构体
type AddResponse struct {
Result int
}
func main() {
addr := "10.0.0.10:8888"
network := "tcp"
// 由于是 rpc 链接所以使用 rpc.Dial
// 创建客户端链接
client, err := rpc.Dial(network, addr)
if err != nil {
fmt.Println(err)
return
}
// 调用编解码过程
req := AddRequest{2, 3} // 传递 2,3 等会交给服务器端的 Calc{} 结构体处理
resq := AddResponse{}
// 调用服务器端方法
// 在调用的时候是 结构体名.方法如:Calc.Add 接调用服务器端的 Calc.Add 方法
// 在 Call 调用的时候肯定会修改 resq 的属性所以这里传递指针
err = client.Call("Calc.Add", req, &resq)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resq)
client.Close()
}
启动客户端,客户端输出 2+3 = 5 ,服务器端输出 calc.Add 方法启动