美文网首页GoGo语言实践
Go语言中的RPC编程

Go语言中的RPC编程

作者: 帅气的昵称都有人用了 | 来源:发表于2019-04-24 09:16 被阅读3次

    RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

    上面就是关于RPC定义的介绍,其实RPC就是想实现函数调用模式的网络化。客户端就可以像调用本地函数一样,然后客户端把这些参数打包后通过网络传递到服务器端,服务器端解包到处理过程中执行,然后执行的结果反馈给客户端。

    具体来说,在运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

    1.调用客户端句柄;执行传送参数
    2.调用本地系统内核发送网络消息
    3.消息传送到远程主机
    4.服务器句柄得到消息并取得参数
    5.执行远程过程
    6.执行的过程将结果返回服务器句柄
    7.服务器句柄返回结果,调用远程系统内核
    8.消息传回本地主机
    9.客户句柄由内核接收消息
    10.客户接收句柄返回的数据**
    

    Go RPC

    Go在标准库中已经支持了RPC,而且支持三个级别的RPC:TCP,HTTP,JSONRPC。但Go中的RPC又都是通过Gob来统一编码的,因此它只能支持在Go语言开发的服务器和客户端之间的交互。
    在被远程访问之前,还需要满足一些条件:

    1)函数必须是导出的(首字母大写)
    2)必须有两个导出类型的参数
    3)第一个参数用来接收,第二个参数是返回给客户端的参数,并且第二个参数必须是指针类型
    4)函数还要有一个返回值error

    格式如下:

    func (t *T) MethodName(arg1 argType, arg2 *argType) error
    

    HTTP RPC

    我们来看一个简单的HTTP的服务器端代码:

    package main
    
    import (
        "errors"
        "log"
        "net/http"
        "net/rpc"
        "os"
    )
    
    type Args struct {
        A, B int
    }
    
    type Quotient struct {
        Quo, Rem int
    }
    
    type Arith int
    
    func (t *Arith) Multiply(args *Args, reply *int) error {
        *reply = args.A * args.B
        return nil
    }
    
    func (t *Arith) Divide(args *Args, quo *Quotient) error {
        if args.B == 0 {
            return errors.New("The divisor cannot be zero.")
        }
        quo.Quo = args.A / args.B
        quo.Rem = args.A % args.B
        return nil
    }
    
    func main() {
        arith := new(Arith)
        rpc.Register(arith)
        rpc.HandleHTTP()
    
        err := http.ListenAndServe(":8000", nil)
        if err != nil {
            log.Fatalln(err.Error())
            os.Exit(1)
        }
    }
    

    我们首先注册了一个Arith的RPC的服务,然后通过rpc.HandleHTTP函数把该服务注册到HTTP协议上,最后就可以利用HTTP的方式来传递数据。
    我们再来看看客户端代码:

    func main() {
        if len(os.Args) != 2 {
            fmt.Println("Usage: ", os.Args[0], "server")
            os.Exit(1)
        }
        serverAddress := os.Args[1]
    
        client, err := rpc.DialHTTP("tcp", serverAddress+":8000")
        if err != nil {
            log.Fatal("Dial Wrong: ",err)
        }
        args := Args{17, 8}
        var reply int
        err = client.Call("Arith.Multiply", args, &reply)
        if err != nil {
            log.Fatal("Arith Wrong: ", err)
        }
        fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, &reply)
    
        var quot Quotient
        err = client.Call("Arith.Divide", args, &quot)
        if err != nil {
            log.Fatal("Arith Wrong: ", err)
        }
        fmt.Printf("Arith: %d/%d=%d and %d\n", args.A, args.B, quot.Quo, quot.Rem)
    }
    

    程序中的关键就在于这个Call函数,它有三个参数,第一个是要调用的函数的名字,第二个要传递的参数,第三个是要返回的参数(必须是指针类型


    TCP RPC

    我们仍使用前面的内容来构建基于TCP的RPC的服务器端:

    func main() {
        arith := new(Arith)
        rpc.Register(arith)
    
        tcpAddr, err := net.ResolveTCPAddr("tcp", "8000")
        checkError(err)
        
        listener, err := net.Listen("tcp", tcpAddr)
        checkError(err)
        
        for {
            conn, err := listener.Accept()
            if err != nil {
                continue
            }
            rpc.ServeConn(conn)
        }
    }
    
    func checkError(err error) {
        if err != nil {
            fmt.Println("Wrong: ", err.Error())
            os.Exit(1)
        }
    }
    

    在这里使用了TCP协议,然后需要自己连接控制,当有客户端连接上来后,需要把这个连接交给RPC来处理。
    相应的RPC客户端代码如下:

    func main() {
        if len(os.Args) != 2 {
            fmt.Println("Usage: ", os.Args[0], "server:port")
            os.Exit(1)
        }
        server := os.Args[1]
        client, err := rpc.Dial("tcp", server)
        if err != nil {
            log.Fatal("Dial Wrong: ", err)
        }
        args := Args{17, 8}
        var reply int
        err = client.Call("Arith.Multiply", args, &reply)
        if err != nil {
            log.Fatal("Arith Wrong: ", err)
        }
        fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, &reply)
    
        var quot Quotient
        err = client.Call("Arith.Divide", args, &quot)
        if err != nil {
            log.Fatal("Arith Wrong: ", err)
        }
        fmt.Printf("Arith: %d/%d=%d and %d\n", args.A, args.B, quot.Quo, quot.Rem)
    }
    

    和HTTP的客户端代码相比,唯一的区别就在于这里使用的是Dial(tcp)函数,而前者使用的是DialHTTP


    JSON RPC

    JSON RPC采用的是json编码的方式,但其操作方式是与上面介绍的RPC方式是基本一致的,和TCP所创建的客户端和服务器端唯一的不同在于,JSON RPC在这里是用的连接方式是jsonrpc.ServerConn(conn)其余的内容完全一致。
    代码在这里不再赘述。


    RPC接口

    Go语言的net/rpc很灵活,它在传输前后实现了编码解码器的接口定义。
    RPC提供的编码解码器接口如下:

    type ClientCodec interface {
        WriteRequest(*Request, interface{}) error
        ReadResponseHeader(*Response) error
        ReadResponseBody(interface{}) errror
        Close() error
    }
    
    type ServerCodes interface {
        ReadRequestHeader(*Request) error
        ReadRequestBody(interface{}) error
        WriteResponse(*Request, interface{}) error
        Close() error
    }
    

    我们可以通过定义很容易的看出来两个编码器的运行过程。
    接口ClientCodec定义了RPC客户端如何在一个RPC会话中发送请求和读取响应。
    接口ServerCodec定义了RPC服务器端如何在一个RPC会话中接收请求并发送响应。

    相关文章

      网友评论

        本文标题:Go语言中的RPC编程

        本文链接:https://www.haomeiwen.com/subject/zmxwgqtx.html