在go语言中,其官方网站的pkg说明中,提供了官方支持的rpc包,指路:
官方提供的rpc完整包名为net/rpc,而rpc包主要是提供通过网络访问一个对象方法的功能。
所以我们实现RPC的调用也是使用了这个rpc包。
Rpc调用的两个参与者分别为客户端(client)和服务器(sever)
服务器就是提供方法暴露的一方。
1.服务的定义及暴露
实际编程过程中,服务器端需要注册结构体对象,通过对象所属方法暴露给调用者,从而提供服务,该方法称之为输出方法,此输出方法可被远程调用。当然因为能够让远程调用所以在定义输出方法的时候要遵循如下的一个格式
Func (t T*) MethodName (request T1, response *T2) error
这里是实现了服务端的功能定义,接下来对服务进行注册,让服务代码生效,启动请求处理。
2.注册服务及监听请求
net/rpc包提供了注册服务和处理请求的一系列方法,可通过下列例子来查看使用方法。
比如给一个float类型的圆形变量要通过rpc调用,返回对应的原型面积,服务端的编码如下:
package main
import (
"math"
"net"
"net/http"
"net/rpc"
)
//main.go == serve.go
//数学计算
type MathUtil struct {
}
//该方法向外暴露,该方法时服务对象MathUtil向外提供的服务方法,即向外提供计算圆形面积的服务
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error {
*resp = math.Pi * req * req
return nil //返回类型
}
//go官方提供的rpc的包规定我们如果想要实现在服务端远程调用一个对外提供服务的方法,必须要实现上面的方法规则
/*
方法有两个函数接收值
第一个req是远程调用者传递过来的请求参数
第二个resp是指针类型,也是我们的返回值类型,返回给调用者的计算结果
函数的返回值必须是error类型,为nil。如果遇到异常或者特殊情况,则error将作为一个字符串返回给调用者,此时,resp这个参数就不会返回给调用者。
函数的接收值也必须是一个指针类型
*/
//main方法
func main() {
//1.初始化指针数据类型
//mathUtil := &MathUtil{}
mathUtil := new(MathUtil) // 初始化指针数据类型
//2.调用net/rpc包的功能将服务对象进行注册
err := rpc.Register(mathUtil)
if err != nil{
panic(err.Error())
}
//3.通过该函数把mathUtil中提供的服务注册到HTTP协议上,方便调用者可以利用http方式进行数据传递,因为要通过网络传递,所以这里要用到http协议
rpc.HandleHTTP()
//4.在特定的端口进行监听
listen, err := net.Listen("tcp", ":8081") //net包中的listen方法会返回一个listen对象
if err != nil {
panic(err.Error())
}
http.Serve(listen, nil) //调用http.Serve来处理这个请求
}
.
3.客户端调用
服务端通过监听http端口来等待连接,所以客户端就可以通过http连接来实现与服务器连接。
连接成功后就可以通过方法来调用服务端的方法了。具体的方法调用分为同步调用方式和异步调用方式。
下面客户端代码就可以展示客户端的调用方式。
package main
import (
"fmt"
"net/rpc"
)
//客户端逻辑实现
func main() {
client, err := rpc.DialHTTP("tcp", "localhost:8081") //指定连接8081端口
if err != nil {
panic(err.Error())
}
var req float32 //请求值
req = 3
/*var resp *float32 //返回值
//同步调用方式
err = client.Call("MathUtil.CalculateCircleArea", req, &resp)
//前面第一个参数就是指定要调用的服务端暴露的函数,第二个是请求参数,第三个是接收参数
//call是一个通用方法,可以同步调用暴露的函数
if err != nil {
panic(err.Error())
}
fmt.Println(*resp)*/
var respSync *float32
//异步调用方式
syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil)
//方法使用了go函数,多了一个参数,最后一个参数为通道变量,借助通道变量来实现异步调用,go返回的是一个指针类型
fmt.Println(*respSync) //在这里加一句则会报错,因为这里是借助通道来实现异步调用,如果没有使用syncCall.Done,关键在于没有使用done函数时不能感知到异步调用的结束,
//使用done函数后,便接收了channel变量,通知异步调用结束,从而返回响应
replayDone := <-syncCall.Done //这里的变量是一个channel类型,所以从通道中读取数据,如果通道中一直没有出现合适的值,那么会一致堵塞在这一行这里。
fmt.Println(replayDone)
fmt.Println(*respSync)
//传递多个参数时,先对参数进行封装。封装成一个结构体,然后进行传递。
}
多参数的请求调用参数传递
上面主要展示了单个参数下的RPC调用,对于多参数下可以参考下面的例子。
比如通过RPC调用实现计算两个数字相加功能并返回对应的计算结果,那么这个时候为了让两个数字相加需要传递两个参数,那么将两个参数封装在一个结构体内,这样就可以实现多参数用单参数进行传递。
先设立一个entity包用来存储封装两个参数的结构体。
package entity
type AddParma struct {
Num1 float32
Num2 float32
}
服务器端:
package main
import (
"Helloworld/entity"
"net"
"net/http"
"net/rpc"
)
type MathUtil struct {
}
func (mu *MathUtil) Add (addParma entity.AddParma, resp *float32) error {
*resp = addParma.Num1 + addParma.Num2
return nil
}
func main() {
mathUtil := &MathUtil{
}
err := rpc.RegisterName("MathUtil", mathUtil)
if err != nil {
panic(err.Error())
}
rpc.HandleHTTP()
listen, err := net.Listen("tcp", "localhost:8082")
http.Serve(listen, nil)
}
客户端:
package main
import (
"Helloworld/entity"
"fmt"
"net/rpc"
)
func main() {
client, err := rpc.DialHTTP("tcp", "localhost:8082")
if err != nil {
panic(err.Error())
}
var resp *float32
addParma := &entity.AddParma{
3.1, 2.1}
err = client.Call("MathUtil.Add", addParma, &resp)
if err != nil {
panic(err.Error())
}
fmt.Println(*resp)
}