1. RPC 简介
⚫ 远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议
⚫ 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程
⚫ 如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用
2. 流行 RPC 框架的对比
3. golang 中如何实现 RPC
⚫ golang 中实现 RPC 非常简单,官方提供了封装好的库,还有一些第三方的库
⚫ golang 官方的 net/rpc 库使用 encoding/gob 进行编解码,支持 tcp 和 http 数据传输方式,由于其他语言不支持 gob 编解码方式,所以 golang 的 RPC 只支持 golang 开发的服务器与客户端之间的交互
⚫ 官方还提供了net/rpc/jsonrpc 库实现RPC 方法,jsonrpc 采用JSON 进行数据编解码,因而支持跨语言调用,目前 jsonrpc 库是基于 tcp 协议实现的,暂不支持 http 传输方式
⚫ golang 的 RPC 必须符合 4 个条件才可以
◼ 结构体字段首字母要大写,要跨域访问,所以大写
◼ 函数名必须首字母大写(可以序列号导出的)
◼ 函数第一个参数是接收参数,第二个参数是返回给客户端参数,必须是指针类型
◼ 函数必须有一个返回值 error
⚫ 例题:golang 实现 RPC 程序,实现求矩形面积和周长
server端
package main
import (
"fmt"
"log"
"net/http"
"net/rpc"
)
// 服务端,求矩形面积和周长
// 声明矩形对象
type Rect struct {
}
// 声明参数结构体,字段首字母大写
type Params struct {
// 长和宽
Width, Height int
}
// 定义求矩形面积的方法
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
//定义求矩形周长的方法
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
func main() {
// 1. 注册服务
rect := new(Rect)
rpc.Register(rect)
// 2.把服务处理绑定到http协议上
rpc.HandleHTTP()
fmt.Println("------ rpc service is already on ------")
// 3. 监听服务,等待客户端调用求周长和面积的方法
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
type Params struct {
Width, Height int
}
// 调用服务
func main(){
// 1. 连接远程RPC服务
rp, err := rpc.DialHTTP("tcp","182.254.179.186:8080")
if err != nil {
log.Fatal(err)
}
// 2.调用远程方法
// 定义接收服务器传回来的计算结果的值
ret := 0
err2 := rp.Call("Rect.Area",Params{50,100},&ret)
if err2!=nil {
log.Fatal(err2)
}
fmt.Println("面积",ret)
//求周长
err3:= rp.Call("Rect.Perimeter",Params{50,100},&ret)
if err3!=nil {
log.Fatal(err3)
}
fmt.Println("周长",ret)
}
⚫ 练习:模仿前面例题,自己实现 RPC 程序,服务端接收 2 个参数,可以做乘法运算,也可以做商和余数的运算,客户端进行传参和访问,得到结果如下:
package main import ( "errors" "fmt" "log" "net/http" "net/rpc" ) type Arith struct { } // 声明接收的参数结构体 type ArithRequest struct { A,B int } // 声明返回客户端的参数结构体 type ArithResponse struct { //乘积 Pro int // 商 Quo int // 余数 Rem int } //乘法 func (this *Arith)Multiply(req ArithRequest,res *ArithResponse)error { res.Pro = req.A * req.B return nil } // 商和余数 func (this *Arith)Divide(req *ArithRequest, res *ArithResponse)error{ if req.B == 0 { return errors.New("出书不能为0") } //商 res.Quo = req.A / req.B //余数 res.Rem = req.A % req.B return nil } func main() { //注册服务 rpc.Register(new(Arith)) // 采用http作为rpc载体 rpc.HandleHTTP() fmt.Println("------ rpc service is already on ------") // 监听服务,等待客户端调用响应的方法 err := http.ListenAndServe(":8081",nil) if err!=nil { log.Fatal(err) } }
package main import ( "fmt" "log" "net/rpc" ) type Arith struct { } // 声明接收的参数结构体 type ArithRequest struct { A,B int } // 声明返回客户端的参数结构体 type ArithResponse struct { //乘积 Pro int // 商 Quo int // 余数 Rem int } func main() { conn, err := rpc.DialHTTP("tcp", "182.254.179.186:8081") //conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8081") if err!= nil{ log.Fatal(err) } req := ArithRequest{9, 2} var res ArithResponse err2 := conn.Call("Arith.Multiply",req, &res) if err2 != nil { log.Println(err2) } fmt.Printf("%d * %d = %d\n", req.A,req.B,res.Pro) err3 := conn.Call("Arith.Divide",req, &res) if err3 != nil { log.Println(err3) } fmt.Printf("%d / %d = %d,%d", req.A,req.B,res.Quo,res.Rem) }