根据上篇内容,了解到RPC的原理及调用过程,本篇则使用Go语言的标准库中的RPC库实现简单的RPC小Demo
包名: net/rpc
首先是提供方法暴露的一方–服务器。
在编程实现过程中,服务器端需要注册结构体对象,然后通过对象所属的方法暴露给调用者,从而提供服务,该方法称之为输出方法,此输出方法可以被远程调用。当然,在定义输出方法时,能够被远程调用的方法需要遵循一定的规则。我们通过代码进行讲解:
func (t *T) MethodName(request T1,response *T2) error
上述代码是go语言官方给出的对外暴露的服务方法的定义标准,其中包含了主要的几条规则,分别是:
我们举例说明:假设目前我们有一个需求,给出一个float类型变量,作为圆形的半径,要求通过RPC调用,返回对应的圆形面积。具体的编程实现思路如下:
type MathUtil struct{
}
//该方法向外暴露:提供计算圆形面积的服务
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error {
*resp = math.Pi * req * req //圆形的面积 s = π * r * r
return nil //返回类型
}
在上述的案例中,我们可以看到:
至此为止,已经实现了服务端的功能定义,接下来就是让服务代码生效,需要将服务进行注册,并启动请求处理。
net/rpc包为我们提供了注册服务和处理请求的一系列方法,结合本案例实现注册及处理逻辑,如下所示:
//1、初始化指针数据类型
mathUtil := new(MathUtil) //初始化指针数据类型
//2、调用net/rpc包的功能将服务对象进行注册
err := rpc.Register(mathUtil)
if err != nil {
panic(err.Error())
}
//3、通过该函数把mathUtil中提供的服务注册到HTTP协议上,方便调用者可以利用http的方式进行数据传递
rpc.HandleHTTP()
//4、在特定的端口进行监听
listen, err := net.Listen("tcp", ":9000")
if err != nil {
panic(err.Error())
}
go http.Serve(listen, nil)
经过服务注册和监听处理,RPC调用过程中的服务端实现就已经完成了。接下来需要实现的是客户端请求代码的实现。
在服务端是通过Http的端口监听方式等待连接的,因此在客户端就需要通过http连接,首先与服务端实现连接。
client, err := rpc.DialHTTP("tcp", "localhost:8081")
if err != nil {
panic(err.Error())
}
var req float32 //请求值
req = 3
var resp *float32 //返回值
err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
if err != nil {
panic(err.Error())
}
fmt.Println(*resp)
上述的调用方法核心在于client.Call方法的调用,该方法有三个参数,第一个参数表示要调用的远端服务的方法名,第二个参数是调用时要传入的参数,第三个参数是调用要接收的返回值。
上述的Call方法调用实现的方式是同步的调用,除此之外,还有一种异步的方式可以实现调用。异步调用代码实现如下:
var respSync *float32
//异步的调用方式
syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil)
replayDone := <-syncCall.Done
fmt.Println(replayDone)
fmt.Println(*respSync)
上述内容演示了单个参数下的RPC调用,对于多参数下的请求该如何实现。我们通过另外一个案例进行演示。
假设现在需要实现另外一个需求:通过RPC调用实现计算两个数字相加功能并返回计算结果。此时,就需要传递两个参数,具体实现如下:
将参数定义在一个新的结构体中,存放在param包中:
type AddParma struct {
Args1 float32 //第一个参数
Args2 float32 //第二个参数
}
在server.go文件中,实现两数相加的功能,并实现服务注册的逻辑:
func (mu *MathUtil) Add(param param.AddParma, resp *float32) error {
*resp = param.Args1 + param.Args2 //实现两数相加的功能
return nil
}
// 1. 初始化指针数据类型
mathUtil := new(MathUtil)
// 2. 调用net/rpc包的功能将服务对象进行注册
err := rpc.RegisterName("MathUtil", mathUtil)
if err != nil {
panic(err.Error())
}
// 3. 通过该函数把mathUtil中提供的服务注册到HTTP协议上,方便调用者通过http的方式调用
rpc.HandleHTTP()
// 4. 在特定的端口上进行监听
listen, err := net.Listen("tcp", ":9000")
http.Serve(listen, nil)
在本案例中,我们通过新的注册方法rpc.RegisterName实现了服务的注册和调用。
package main
import (
"fmt"
"net"
"net/http"
"net/rpc"
)
type AddParam struct {
Args1 float32
Args2 float32
}
// 数学计算
type MathUtil struct {
}
// 向外暴露计算的方法
func (mu *MathUtil) Add(req AddParam, resp *float32) error {
*resp = req.Args1 + req.Args2
fmt.Println(*resp)
return nil
}
// main方法
func main() {
// 1. 初始化指针数据类型
mathUtil := new(MathUtil)
// 2. 调用net/rpc包的功能将服务对象进行注册
err := rpc.Register(mathUtil)
if err != nil {
panic(err.Error())
}
// 3. 通过该函数把mathUtil中提供的服务注册到HTTP协议上,方便调用者通过http的方式调用
rpc.HandleHTTP()
// 4. 在特定的端口上进行监听
listen, err := net.Listen("tcp", ":9000")
if err != nil {
panic(err.Error())
}
_ = http.Serve(listen, nil)
}
package main
import (
"fmt"
"net/rpc"
)
type AddParam2 struct {
Args1 float32
Args2 float32
}
func main() {
// 拨号
client, err := rpc.DialHTTP("tcp", "localhost:9000")
if err != nil {
panic(err.Error())
}
// 请求值
var req = AddParam2{1, 2}
//var resp *float32
同步调用的方式
//err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
//if err != nil {
// panic(err)
//}
//fmt.Println(*resp)
var respSync *float32
//异步的调用方式
syncCall := client.Go("MathUtil.Add", req, &respSync, nil)
replayDone := <-syncCall.Done // 堵塞,直到成功
fmt.Println(replayDone)
fmt.Println(*respSync)
}