RPC 协议

RPC定义

Remote Procedure Call Protocol : 远程过程调用协议!
RPC:远程进通信 , 应用层协议(http协议同层)。底层使用 TCP 实现。
IPC: 进程间通信

  • 像调用本地函数一样,调用远程函数。

  • 通过rpc协议,传递:函数名、函数参数。达到在本地,调用远端函数,得返回值到本地的目标。

RPC 使用

服务端:

  1. 注册 rpc 服务对象。给对象绑定方法( 1. 定义类, 2. 绑定类方法 )
    rpc.RegisterName("服务名",回调对象)
    
  2. 创建监听器
    listener, err := net.Listen()
    
  3. 建立连接
    conn, err := listener.Accept()
    
  4. 将连接 绑定 rpc 服务。
    rpc.ServeConn(conn)
    

客户端:

  1. 用 rpc 连接服务器。
    conn, err := rpc.Dial()
    
  2. 调用远程函数。
    conn.Call("服务名.方法名", 传入参数, 传出参数)
    

RPC 相关函数

  1. 注册 rpc 服务
    func (server *Server) RegisterName(name string, rcvr interface{}) error
        参1:服务名。字符串类型。
        参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 变量,&变量名  完成传参。
    

编码实现

server

package main

import (
    "fmt"
    "net"
    "net/rpc"
)

// World 定义类对象
type World struct {
}

func (this *World) HelloWorld(name string, resp *string) error {
    *resp = name + "hello !!"
    return nil
}

func main() {
    // 1.注册RPC服务
    err := rpc.RegisterName("xiaosheng", new(World))
    if err != nil {
        fmt.Println("注册服务失败")
    }
    // 2.设置监听
    listener, err := net.Listen("tcp", "127.0.0.1:8700")
    if err != nil {
        fmt.Println("net listen err:", err)
        return
    }
    defer listener.Close()
    fmt.Println("[*]:开始监听---")
    // 3.建立链接
    conn, err := listener.Accept()
    if err != nil {
        fmt.Println("Accept () err:", err)
        return
    }
    defer conn.Close()
    fmt.Println("[*]:链接成功---")
    // 4.绑定服务
    rpc.ServeConn(conn)
}

client

package main

import (
    "fmt"
    "net/rpc"
)

func main() {
    // 1.用rpc去链接服务器
    conn, err := rpc.Dial("tcp", "127.0.0.1:8700")
    if err != nil {
        fmt.Println("Dial err:", err)
        return
    }
    defer conn.Close()
    // 2.调用远程函数
    reply := ""
    err = conn.Call("xiaosheng.HelloWorld", "xiaosheng", &reply)
    if err != nil {
        fmt.Println("Call err:", err)
    }
    fmt.Println(reply)
}

json 版 rpc

  • 使用 nc -l 127.0.0.1 8700 充当服务器。
  • client.go 充当 客户端。 发起通信。会产生乱码。
    • 因为:RPC 使用了go语言特有的数据序列化 gob。 其他编程语言不能解析。
  • 解决方法: 使用 通用的 序列化、反序列化。 —— json、protobuf

jsonrpc客户端

修改客户端,使用jsonrpc

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

jsonrpc server端

修改服务器端,使用 jsonrpc

jsonrpc.ServeConn(conn)

简单替换一下就行

另外:如果,绑定方法返回值的 error 不为空, 那么无论传出参数是否有值,服务端都不会返回数据。

rpc 封装

Server端封装

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

    例子

  • server

  • client

客户端封装

  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}
    }
    

封装的demo

// client
package main

import (
    "fmt"
)

func main() {
    // 1.用rpc去链接服务器
    myclient := InitClient("127.0.0.1:8700")

    // 2.调用远程函数
    reply := ""
    err := myclient.HelloWorld("xiaosheng", &reply)
    if err != nil {
        fmt.Println("Call err:", err)
    }
    fmt.Println(reply)
}
// design封装client server
package main

import "net/rpc"

// -------------Server------------
//要求 服务端在注册rpc时要检测出注册对象是否合法

// MyInterface 创建接口, 在接口中定义原型方法
type MyInterface interface {
    HelloWorld(string, *string) error
}

func RegisterService(i MyInterface) {
    rpc.RegisterName("xiaosheng", i)
}

// -------------Client------------

// MyClient 像调用本地函数一样调用远程函数
type MyClient struct {
    c *rpc.Client
}

// InitClient 由于使用了c来调用Call ,因此需要调用c.Dail来初始化,此处需要看client的实现
func InitClient(addr string) MyClient {
    conn, _ := rpc.Dial("tcp", addr)
    return MyClient{conn}
}

// HelloWorld 实现函数,原型按照上面 Interface来实现
func (this *MyClient) HelloWorld(a string, b *string) error {
    this.c.Call("xiaosheng.HelloWorld", a, b)
    return nil
}
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

// World 定义类对象
type World struct {
}

func (this *World) HelloWorld(name string, resp *string) error {
    *resp = name + "hello !!"
    return nil
}

func main() {
    // 1.注册RPC服务
    RegisterService(new(World))   // 封装的,调用的Design
    // 2.设置监听
    listener, err := net.Listen("tcp", "127.0.0.1:8700")
    if err != nil {
        fmt.Println("net listen err:", err)
        return
    }
    defer listener.Close()
    fmt.Println("[*]:开始监听---")
    // 3.建立链接
    conn, err := listener.Accept()
    if err != nil {
        fmt.Println("Accept () err:", err)
        return
    }
    defer conn.Close()
    fmt.Println("[*]:链接成功---")
    // 4.绑定服务
    rpc.ServeConn(conn)
}
分类: micro-service

站点统计

  • 文章总数:315 篇
  • 分类总数:20 个
  • 标签总数:193 个
  • 运行天数:1147 天
  • 访问总数:26819 人次

浙公网安备33011302000604

辽ICP备20003309号