通过学习和试验,感觉go的rpc非常的方便易用,下面就将学习的过程总结一下。
RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序之间携带信息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
Go语言标准库能够自带一个rpc框架还是非常给力的,这可以很大程度的降低写后端网络通信服务的门槛,特别是在大规模的分布式系统中,rpc基本是跨机器通信的标配。rpc能够最大程度屏蔽网络细节,让开发者专注在服务功能的开发上面。Go标准包支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。
Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:
举个例子,正确的RPC函数格式如下:
func (t *T) GoRPCMethodName(argType T1, returnType *T2) error
T、T1和T2类型必须能被encoding/gob包编解码。
任何的RPC都需要通过网络来传递数据,Go RPC可以利用HTTP和TCP来传递数据,利用HTTP的好处是可以直接复用net/http里面的一些函数。下面以最简单的示例分别进行说明。环境所限,以下代码我都是将服务器端跑在win7上,客户端跑在centos6.4虚拟机上。
服务器端示例代码:
package main import ( "fmt" "net/http" "net/rpc" ) var S string type MyRPC int func (r *MyRPC) HelloRPC(S string, reply *string) error { fmt.Println(S) *reply = "This Server. Hello Client RPC." return nil } func main() { r := new(MyRPC) rpc.Register(r) rpc.HandleHTTP() err := http.ListenAndServe("172.16.34.222:1234", nil) if err != nil { fmt.Println("in main", err.Error()) } }
客户端示例代码:
package main import ( "fmt" "net/rpc" "os" "log" ) var S string func main() { if len(os.Args) != 2 { fmt.Println("usage: ", os.Args[0], "ip:port") os.Exit(1) } addr := os.Args[1] client, err := rpc.DialHTTP("tcp", addr) if err != nil { log.Fatal("dialhttp: ", err) } var reply *string S = "This Client. Hello Server RPC." err = client.Call("MyRPC.HelloRPC", S, &reply) if err != nil { log.Fatal("call hellorpc: ", err) } fmt.Println(*reply) }
通过上面的调用可以看到参数和返回值是我们定义的string类型;在服务端把它们当做调用函数的参数,在客户端作为client.Call的第2,3两个参数。客户端最重要的就是Call函数,它有3个参数,第1个是要调用的函数的名字,第2个是要传递的参数,第3个是要返回的参数(指针类型)。
服务器端示例代码:
package main import ( "fmt" "net" "net/rpc" "os" ) var S string type MyRPC int func (r *MyRPC) HelloRPC(S string, reply *string) error { fmt.Println(S) *reply = "This Server. Hello Client RPC." return nil } func main() { r := new(MyRPC) rpc.Register(r) tcpAddr, err := net.ResolveTCPAddr("tcp", "172.16.34.222:1234") checkError(err) listener, err := net.ListenTCP("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("Fatal error ", err.Error()) os.Exit(1) } }
客户端示例代码:
package main import ( "fmt" "net/rpc" "os" "log" ) var S string func main() { if len(os.Args) != 2 { fmt.Println("usage: ", os.Args[0], "ip:port") os.Exit(1) } addr := os.Args[1] client, err := rpc.Dial("tcp", addr) if err != nil { log.Fatal("dialhttp: ", err) } var reply *string S = "This Client. Hello Server RPC." err = client.Call("MyRPC.HelloRPC", S, &reply) if err != nil { log.Fatal("call hellorpc: ", err) } fmt.Println(*reply) }
JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介绍的RPC概念一模一样。
服务器端示例代码:
package main import ( "fmt" "net" "net/rpc" "net/rpc/jsonrpc" "os" ) var S string type MyRPC int func (r *MyRPC) HelloRPC(S string, reply *string) error { fmt.Println(S) *reply = "This Server. Hello Client RPC." return nil } func main() { r := new(MyRPC) rpc.Register(r) tcpAddr, err := net.ResolveTCPAddr("tcp", "172.16.34.222:1234") checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } jsonrpc.ServeConn(conn) } } func checkError(err error) { if err != nil { fmt.Println("Fatal error ", err.Error()) os.Exit(1) } }
客户端示例代码:
package main import ( "fmt" "net/rpc/jsonrpc" "os" "log" ) var S string func main() { if len(os.Args) != 2 { fmt.Println("usage: ", os.Args[0], "ip:port") os.Exit(1) } addr := os.Args[1] client, err := jsonrpc.Dial("tcp", addr) if err != nil { log.Fatal("dialhttp: ", err) } var reply *string S = "This Client. Hello Server RPC." err = client.Call("MyRPC.HelloRPC", S, &reply) if err != nil { log.Fatal("call hellorpc: ", err) } fmt.Println(*reply) }