五、RPC包的使用

RPC的使用

包rpc提供了通过网络访问一个对象的输出方法的能力。服务端注册一个对象,使它作为一个服务被暴露,服务的名字是该对象的类型名。注册之后,对象的导出方法就可以被远程访问。服务端可以注册多个不同类型的对象(服务),但注册具有相同类型的多个对象是错误的。

只有满足如下标准的方法才能用于远程访问,其余方法会被忽略:
a、方法是导出的
b、方法有两个参数,都是导出类型或内建类型
c、方法的第二个参数是指针
d、方法只有一个error接口类型的返回值
e、注册类型也是可导出的
形式如下:

func (t *T) MethodName(argType T1, replyType *T2) error
1、一般类型的RPC

服务端案例:

client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
    log.Fatal("dialing:", err)
}

var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
    log.Fatal(err)
}

fmt.Println(reply)

客户端案例:

type HelloService struct {}

func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}

func main () {
    rpc.RegisterName("HelloService", new(HelloService))

    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }

    rpc.Accept(listener)

    rpc.HandleHTTP()
}
2、RPC使用优化

在涉及RPC的应用中,作为开发人员,一般有三种角色:
a、服务端实现RPC方法的开发人员
b、客户端调用RPC方法的人员
c、制定服务端和客户端RPC接口规范的设计人员

现在我们来重构我们的RPC代码:
2.1、RPC的接口规范,我们将RPC服务的接口规范分为三个部分
a、明确Service服务的名字
b、明确Service服务要实现的详细方法列表
c、明确注册该类型Service服务的函数
案例如下:

//确定服务的名字,为了避免名字冲突,我们在RPC服务的名字中增加了包路径前缀【这个是RPC服务抽象的包路径,并非完全等价Go语言的包路径】
const HelloServiceName = "path/to/pkg.HelloService"

//明确Service服务要实现的详细方法列表
type HelloServiceInterface = interface {
    Hello(request string, reply *string) error
}

//明确注册该类型Service服务的函数
func RegisterHelloService(svc HelloServiceInterface) error {
    return rpc.RegisterName(HelloServiceName, svc)
}

接口规范定好之后,我们就可以根据规范进行我们的RPC开发了
为了简化客户端用户调用RPC函数,我们在可以在接口规范部分增加对客户端的简单包装:

type HelloServiceClient struct {
    *rpc.Client
}

var _ HelloServiceInterface = (*HelloServiceClient)(nil)

func DialHelloService(network, address string) (*HelloServiceClient, error) {
    c, err := rpc.Dial(network, address)
    if err != nil {
    return nil, err
    }
    return &HelloServiceClient{Client: c}, nil
}

func (p *HelloServiceClient) Hello(request string, reply *string) error {
    return p.Client.Call(HelloServiceName+".Hello", request, reply)
}

Service服务端代码案例

type HelloService struct {}

func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}

func main() {
    RegisterHelloService(new(HelloService))

    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal("Accept error:", err)
        }

        go rpc.ServeConn(conn)
    }
}

Client客户端代码案例:

func main() {
    client, err := DialHelloService("tcp", "localhost:1234")
    if err != nil {
    log.Fatal("dialing:", err)
    }

    var reply string
    err = client.Hello("hello", &reply)
    if err != nil {
    log.Fatal(err)
    }
}
3、跨语言的RPC

标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。在互联网的微服务时代,每个RPC以及服务的使用者都可能采用不同的编程语言,因此跨语言是互联网时代RPC的一个首要条件。得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。

Go语言的RPC框架有两个比较有特色的设计:一个是RPC数据打包时可以通过插件实现自定义的编码和解码;另一个是RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上。这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的PPC。

RPC服务:

func main() {
    rpc.RegisterName("HelloService", new(HelloService))

    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal("Accept error:", err)
        }

        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

RPC客户端:

func main() {
    conn, err := net.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("net.Dial:", err)
    }

    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

    var reply string
    err = client.Call("HelloService.Hello", "hello", &reply)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(reply)
}
4、Http上的RPC

Go语言内在的RPC框架已经支持在Http协议上提供RPC服务。但是框架的http服务同样采用了内置的gob协议,并且没有提供采用其它协议的接口,因此从其它语言依然无法访问的。在前面的例子中,我们已经实现了在TCP协议之上运行jsonrpc服务,并且通过nc命令行工具成功实现了RPC方法调用。现在我们尝试在http协议上提供jsonrpc服务

func main() {
    rpc.RegisterName("HelloService", new(HelloService))

    http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
        var conn io.ReadWriteCloser = struct {
            io.Writer
            io.ReadCloser
        }{
            ReadCloser: r.Body,
            Writer:     w,
        }

        rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
    })

    http.ListenAndServe(":1234", nil)
}

你可能感兴趣的:(五、RPC包的使用)