RPC简介

RPC简介

Remote Procedure Call Protocol —— 远程过程调用协议

RPC(Remote Procedure Call Protocol),是远程过程调用的缩写,通俗的说就是调用远处的一个函数。

  • 理解RPC:

    • ==像调用本地函数一样,去调用远程函数。==
      • 通过rpc协议,传递:函数名、函数参数。达到在本地,调用远端函数,得返回值到本地的目标。
  • 为什么微服务使用 RPC:

    1. 每个服务都被封装成 进程。彼此”独立“。
    2. 进程和进程之间,可以使用不同的语言实现。

    我们使用微服务化的一个好处就是,不限定服务的提供方使用什么技术选型,能够实现公司跨团队的技术解耦

    image-20211223195454081

    如果没有统一的服务框架,RPC框架,各个团队的服务提供方就需要各自实现一套序列化、反序列化、网络框架、连接池、收发线程、超时处理、状态机等“业务之外”的重复技术劳动,造成整体的低效。所以,统一RPC框架把上述“业务之外”的技术劳动统一处理,是服务化首要解决的问题。

RPC 使用的步骤

—- 服务端:

  1. 注册 rpc 服务对象。给对象绑定方法( 1. 定义类, 2. 绑定类方法 )

    rpc.RegisterName("服务名",回调对象)
  2. 创建监听器

    listener, err := net.Listen()
  3. 建立连接

    conn, err := listener.Accept()
  4. 将连接 绑定 rpc 服务。

    rpc.ServeConn(conn)
// 定义类对象
type World struct {
}
//定义类方法
func (w *World) Hello(name string,resp *string)error {
    *resp=name+" hello!"
    return nil
}
func (w *World) Bye(name string, resp *string) error {
    *resp=name+" Bye!"
    return nil
}
func main(){
    //1. 注册RPC服务, 绑定对象方法
    err := rpc.RegisterName("Hello", new(World))
    //
    if err != nil {
        fmt.Println("rpc.RegisterName err:",err)
        return
    }
    //2.设置监听
    listener, err := net.Listen("tcp", "127.0.0.1:8848")
    if err != nil {
        fmt.Println("net.Listen err:",err)
        return
    }
    defer listener.Close()
    fmt.Println("开始监听")
    //3./ Accept 建立连接
    conn, err := listener.Accept()
    if err != nil {
        fmt.Println("listener.Accept err:",err)
        return
    }
    defer conn.Close()
    fmt.Println("连接建立成功")
    //绑定服务
    rpc.ServeConn(conn)

}

—- 客户端:

  1. 用 rpc 连接服务器。

    conn, err := rpc.Dial()
  2. 调用远程函数。

    conn.Call("服务名.方法名", 传入参数, 传出参数)
package main

import (
    "fmt"
    "net/rpc"
)

func main()  {
    // 1. 用 rpc 链接服务器 --Dial() 注意要使用rpc下的Dial
    conn, err := rpc.Dial("tcp", "127.0.0.1:8848")
    if err != nil {
        fmt.Println("rpc.Dial err:",err)
        return
    }
    defer conn.Close()
    // 2. 调用远程函数
    var resp string
    //err = conn.Call("Hello.Hello", "zhu", &resp)
    err = conn.Call("Hello.Bye", "zhu", &resp)
    if err!=nil {
        fmt.Println("conn.Call err:",err)
        return
    }
    fmt.Println(resp)

}

RPC 相关函数

  1. 注册 rpc 服务

    func (server *Server) RegisterName(name string, rcvr interface{}) error1:服务名。字符串类型。
        参2:对应 rpc 对象。 该对象绑定方法要满足如下条件:
            1)方法必须是导出的 —— 包外可见。 首字母大写。
            2)方法必须有两个参数, 都是导出类型、內建类型。
            3)方法的第二个参数必须是 “指针” (传出参数)
            4)方法只有一个 error 接口类型的 返回值。
    举例:
    
    type World stuct {
    }        
    func (this *World) HelloWorld (name string, resp *string) error { 
    }
    rpc.RegisterName("服务名"new(World))
  2. 绑定 rpc 服务

    func (server *Server) ServeConn(conn io.ReadWriteCloser)
        conn: 成功建立好连接的 socket —— conn
  3. 调用远程函数:

    func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
        serviceMethod: “服务名.方法名”
        args:传入参数。 方法需要的数据。
        reply:传出参数。定义 var 变量,&变量名  完成传参。

json 版 rpc

修改客户端

修改客户端,使用jsonrpc:

conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8800")
package main

import (
    "errors"
    "fmt"
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "os"
)

// 算数运算结构体
type Arith struct {
}

// 算数运算请求结构体
type ArithRequest struct {
    A int
    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("divide by zero")
    }
    res.Quo = req.A / req.B
    res.Rem = req.A % req.B
    return nil
}

func main() {
    rpc.Register(new(Arith)) // 注册rpc服务

    lis, err := net.Listen("tcp", "127.0.0.1:8096")
    if err != nil {
        log.Fatalln("fatal error: ", err)
    }

    fmt.Fprintf(os.Stdout, "%s", "start connection")

    for {
        conn, err := lis.Accept() // 接收客户端连接请求
        if err != nil {
            continue
        }

        go func(conn net.Conn) { // 并发处理客户端请求
            fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
            jsonrpc.ServeConn(conn)
        }(conn)
    }
}

修改服务器端

修改服务器端,使用 jsonrpc:

jsonrpc.ServeConn(conn)
package main

import (
    "fmt"
    "log"
    "net/rpc/jsonrpc"
)
// 算数运算请求结构体
type ArithRequest1 struct {
    A int
    B int
}

// 算数运算响应结构体
type ArithResponse1 struct {
    Pro int // 乘积
    Quo int // 商
    Rem int // 余数
}

func main() {
    conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8096")
    if err != nil {
        log.Fatalln("dailing error: ", err)
    }

    req := ArithRequest1{9, 2}
    var res ArithResponse1

    err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
    if err != nil {
        log.Fatalln("arith error: ", err)
    }
    fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)

    err = conn.Call("Arith.Divide", req, &res)
    if err != nil {
        log.Fatalln("arith error: ", err)
    }
    fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

rpc 封装

服务端封装

  1. // 定义接口
    type xxx interface {
        方法名(传入参数,传出参数) error
    }
    例:
    type MyInterface interface {
        HelloWorld(string, *string) error
    }
  2. // 封装注册服务方法
    func RegisterService (i MyInterface) {
        rpc.RegisterName("hello", i)
    }
type MicroService interface {
    Hello(string,*string) error
    Bye(string,*string)error
}
type HelloService struct {
}

func (h *HelloService) Hello(name string, resp *string) error {
    *resp=name+" hello!"
    return nil}

func (h *HelloService) Bye(name string, resp *string) error {
    *resp=name+" bye!"
    return nil}

func RegisterService(name string,service MicroService){
    rpc.RegisterName(name,service)
}

客户端封装

  1. // 定义类
    type MyClient struct {
        c *rpc.Client
    }
  2. // 绑定类方法
    func (this *MyClient)HelloWorld(a string, b *string) error {
       return  this.c.Call("hello.HelloWorld", a, b)
    }
  3. // 初始客户端
    func InitClient(addr string) error {
        conn, _ := jsonrpc.Dial("tcp", adddr)
        return MyClient{c:conn}
    }
type MyClient struct {
    c *rpc.Client
}

func (client *MyClient) Hello(a string,b *string)error  {
    return client.c.Call("Hello.Hello", a, b)
}
// 初始客户端
func InitClient(addr string) (MyClient,error) {
    conn, err := rpc.Dial("tcp", addr)
    return MyClient{c:conn},err
}
func main() {
    client, err := InitClient("127.0.0.1:8848")
    if err!=nil {
        fmt.Println(err)
        return
    }
    var resp string
    //不能定义var resp *string,再传入resp,因为resp为指针类型,初始值为nil,所以不能找到这个指针
    err = client.Hello("zhu", &resp)
    if err!=nil {
        fmt.Println("client.Hello err:",err)
    }
    fmt.Println(resp)
}

   转载规则


《RPC简介》 朱林刚 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
protobuf protobuf
protobuf简介Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式
2021-12-24
下一篇 
TCP详解 TCP详解
TCP详解TCP报文TCP报文格式 序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文
  目录