Golang 简易RPC的实现

客户端:服务调用发起方,又称之为服务消费者 服务器:远端计算机上运行的程序,其中包含客户端要调用和访问的方法 客户端存根:存放服务器端的地址,端口消息,将客户端的请求参数打包成网络消息,发送给服务器方。接收服务器方返回的数据包。该段程序运行在客户端。 服务端存根:接收客户端发送的数据包,解析数据包,调用数据包,调用具体的服务方法,将调用结果打包发送给客户端一方。该段程序运行在服务端。编程

工做过程:json

一、客户端想要发起一个远程过程调用,首先经过调用本地客户端Stub程序的方式调用想要使用的功能方法名;服务器

二、客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息作序列化操做,并打包成数据包。网络

三、客户端Stub查找到远程服务器程序的IP地址,调用Socket通讯协议,经过网络发送给服务端。异步

四、服务端Stub程序接收到客户端发送的数据包信息,并经过约定好的协议将数据进行反序列化,获得请求的方法名和请求参数等信息。tcp

五、服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。ide

六、服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。编码

七、服务端Stub程序将程序调用结果按照约定的协议进行序列化,并经过网络发送回客户端Stub程序。代理

八、客户端Stub程序接收到服务端Stub发送的返回数据,对数据进行反序列化操做,并将调用返回的数据传递给客户端请求发起者。指针

九、客户端请求发起者获得调用结果,整个RPC调用过程结束。

RPC涉及到的相关技术 经过上文一系列的文字描述和讲解,咱们已经了解了RPC的由来和RPC整个调用过程。咱们能够看到RPC是一系列操做的集合,其中涉及到不少对数据的操做,以及网络通讯。所以,咱们对RPC中涉及到的技术作一个总结和分析:

一、动态代理技术: 上文中咱们提到的Client Stub和Sever Stub程序,在具体的编码和开发实践过程当中,都是使用动态代理技术自动生成的一段程序。

二、序列化和反序列化: 在RPC调用的过程当中,咱们能够看到数据须要在一台机器上传输到另一台机器上。在互联网上,全部的数据都是以字节的形式进行传输的。而咱们在编程的过程当中,每每都是使用数据对象,所以想要在网络上将数据对象和相关变量进行传输,就须要对数据对象作序列化和反序列化的操做。

序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。

反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。

服务定义及暴漏:

func (t *T) MethodName(request T1,response *T2) error 上述代码是go语言官方给出的对外暴露的服务方法的定义标准,其中包含了主要的几条规则,分别是: 一、对外暴露的方法有且只能有两个参数,这个两个参数只能是输出类型或内建类型,两种类型中的一种。 二、方法的第二个参数必须是指针类型。 三、方法的返回类型为error。 四、方法的类型是可输出的。 * 五、方法自己也是可输出的。

type MathUtil struct{}//该方法向外暴露:提供计算圆形面积的服务
func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error {
    *resp = math.Pi * req * req //圆形的面积 s = π * r * r
    return nil //返回类型
}

代码讲解: 在上述的案例中,咱们能够看到: 一、Calculate方法是服务对象MathUtil向外提供的服务方法,该方法用于接收传入的圆形半径数据,计算圆形面积并返回。 二、第一个参数req表明的是调用者(client)传递提供的参数。 三、第二个参数resp表明要返回给调用者的计算结果,必须是指针类型。 四、正常状况下,方法的返回值为是error,为nil。若是遇到异常或特殊状况,则error将做为一个字符串返回给调用者,此时,resp参数就不会再返回给调用者。

客户端链接服务端:

client, err := rpc.DialHTTP("tcp", "localhost:8081")

if err != nil { 
panic(err.Error())
}

远端方法调用 客户端成功链接服务端之后,就能够经过方法调用调用服务端的方法,具体调用方法以下:

var req float32 //请求值 req = 3
var resp float32 //返回值 
err = client.Call("MathUtil.CalculateCircleArea", req, &resp) 
if err != nil { 
panic(err.Error()) 
} 
fmt.Println(resp)

上述的调用方法核心在于client.Call方法的调用,该方法有三个参数,第一个参数表示要调用的远端服务的方法名,第二个参数是调用时要传入的参数,第三个参数是调用要接收的返回值。 上述的Call方法调用实现的方式是同步的调用,除此以外,还有一种异步的方式能够实现调用。异步调用代码实现以下:

var respSync *float32
//异步的调用方式 
syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil)
replayDone := <-syncCall.Done
fmt.Println(replayDone)
fmt.Println(*respSync)

代码演示:

Go HTTPRPC

//server
package main


import (
    "errors"
    "fmt"
    "log"
    "net/http"
    "net/rpc"
)


//使用 rpc http实现简单的 rpc操做
type Args struct {
    A, B int
}


type Math int


func (m *Math) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}


type Quotient struct {
    Quo, Rem int
}


func (m *Math) Divide(args *Args, quo *Quotient) error {
    if args.B == 0 {
        return errors.New("divide by zero")
    }


    quo.Quo = args.A / args.B


    quo.Rem = args.A % args.B


    return nil
}


func main() {
    math := new(Math)
    rpc.Register(math) //注册rpc
    rpc.HandleHTTP()   //使用http rpc


    fmt.Println("rpc http server runing ....")
    err := http.ListenAndServe(":1234", nil)


    if err != nil {
        log.Println(err.Error())
    }
}

//client
package main


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


type Args struct {
    A, B int
}


type Quotient struct {
    Quo, Rem int
}


func main() {


    // fmt.Println(os.Args)


    // if len(os.Args) != 2 {
    //  fmt.Println("usage:", os.Args[0], "server")
    //  os.Exit(1)
    // }


    serverAddr := "127.0.0.1"


    client, err := rpc.DialHTTP("tcp", serverAddr+":1234")


    if err != nil {
        log.Println("dial err is ", err)
    }


    var reply int


    args := Args{1, 2}


    err = client.Call("Math.Multiply", args, &reply) //server method方法名要与server一致,写错后,将会提示服务不存在


    if err != nil {
        log.Println("call err ", err)
    }


    fmt.Printf("Math Multiply: %d * %d =%d \n", args.A, args.B, reply)


    var quo Quotient
    err = client.Call("Math.Divide", args, &quo)


    if err != nil {
        log.Println("divide err ", err)
    }
    fmt.Printf("Math Divide: %d / %d =%d remainder %d \n", args.A, args.B, quo.Quo, quo.Rem)


}

Go TcpRPC

//server
package main


import (
    "errors"
    "log"
    "net"
    "net/rpc"
)


type Math int


type Args struct {
    A, B int
}


func (m *Math) Multiply(args *Args, reply *int) error {


    *reply = args.A * args.B


    return nil
}


type Quotient struct {
    Quo, Rem int
}


func (m *Math) Divide(args *Args, quo *Quotient) error {


    if args.B == 0 {
        return errors.New("divide by zero")
    }


    quo.Quo = args.A / args.B
    quo.Rem = args.A % args.B


    return nil
}


func main() {


    math := new(Math)


    rpc.Register(math)


    tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")


    if err != nil {
        log.Println("Resolve Ip addr err ", err)
        return
    }


    listen, err := net.ListenTCP("tcp", tcpAddr)


    if err != nil {
        log.Println("listen err is ", err)
        return
    }


    for {
        server, err := listen.Accept()
        if err != nil {
            log.Println("accept err is ", err)
            continue
        }
        rpc.ServeConn(server)
    }
}

//client

package main


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


type Args struct {
    A, B int
}


type Quotient struct {
    Quo, Rem int
}


func main() {


    client, err := rpc.Dial("tcp", "127.0.0.1"+":1234")


    if err != nil {
        log.Println("rpc dial err :", err)
        return
    }


    args := Args{1, 2}
    var reply int
    err = client.Call("Math.Multiply", args, &reply)


    if err != nil {
        log.Println("multiply err ", err)
        return
    }


    fmt.Printf("multiply %d * %d = %d  \n", args.A, args.B, reply)


    var quo Quotient


    err = client.Call("Math.Divide", args, &quo)


    if err != nil {
        log.Println("Divite err is ", err)
        return
    }


    fmt.Printf("Divide %d / %d =%d ,rem %d", args.A, args.B, quo.Quo, quo.Rem)


}

Go JSONRPC

//server
package main


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




type Math int


type Args struct {
    A, B int
}


type Quotient struct {
    Quo, Rem int
}


func (m *Math)Multiply(args Args,reply *int) error{
    *reply=args.A*args.B


    return nil
}




func (m *Math)Divide(args Args,quo *Quotient) error{
    if args.B==0{
        return errors.New("divide by zreo")
    }


    quo.Quo=args.A/args.B
    quo.Rem=args.A%args.B
    
    return nil
}


func main(){


    math:=new(Math)


    rpc.Register(math)


    listenAddr,err:=net.ResolveTCPAddr("tcp",":1234")
    if err!=nil{
        log.Println("resove ip addr err is ",err)
        return
    }


    listen,err:=net.ListenTCP("tcp",listenAddr)


    if err!=nil{
        log.Println("listenTcp err ",err)
        return
    }


    for{
        conn,err:=listen.Accept()


        if err!=nil{
            log.Println("accept err is ",err)
            return
        }
        jsonrpc.ServeConn(conn)
    }
    
}

//client

package main


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


type Args struct {
    A, B int
}


type Quotient struct {
    Quo, Rem int
}


func main() {


    client, err := jsonrpc.Dial("tcp", "127.0.0.1"+":1234")


    if err != nil {
        log.Println("rpc dial err :", err)
        return
    }


    args := Args{1, 2}
    var reply int
    err = client.Call("Math.Multiply", args, &reply)


    if err != nil {
        log.Println("multiply err ", err)
        return
    }


    fmt.Printf("multiply %d * %d = %d  \n", args.A, args.B, reply)


    var quo Quotient


    err = client.Call("Math.Divide", args, &quo)


    if err != nil {
        log.Println("Divite err is ", err)
        return
    }


    fmt.Printf("Divide %d / %d =%d ,rem %d", args.A, args.B, quo.Quo, quo.Rem)


}
相关文章
相关标签/搜索