通过学习和试验,感觉go的rpc非常的方便易用,下面就将学习的过程总结一下。
1. 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的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:
- 函数必须是导出的(首字母大写)
- 必须有两个导出类型的参数
- 第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的
- 函数还要有一个返回值error
举个例子,正确的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虚拟机上。
2. HTTP RPC
服务器端示例代码:
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/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())
}
}
对象r是为了方便调用HelloRPC方法,参数S接收客户端传过来的参数,reply声明为指针类型,才能传递到客户端。编辑的时候,将ip改成自己的ip就可以了。服务器端通过rpc.HandleHTTP函数把该服务注册到HTTP协议上,然后就可以利用http的方式来传递数据了。
客户端示例代码:
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)
}
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)
}
指定变量S,用来向服务器端传递第一个参数内容,reply声明为指针类型,用来取得服务器端的reply内容。
通过上面的调用可以看到参数和返回值是我们定义的string类型;在服务端把它们当做调用函数的参数,在客户端作为client.Call的第2,3两个参数。客户端最重要的就是Call函数,它有3个参数,第1个是要调用的函数的名字,第2个是要传递的参数,第3个是要返回的参数(指针类型)。
3. TCP RPC
服务器端示例代码:
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"
"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)
}
}
TCP RPC采用了TCP协议,需要自己控制连接,当有客户端连接上来后,需要把这个连接交给rpc来处理,也就是
rpc.ServeConn(conn)。
客户端示例代码:
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)
}
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)
}
客户端代码唯一需要改变的地方就是
rpc.DialHTTP("tcp", addr
),变为
rpc.Dial("tcp", addr)。
4. JSON RPC
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"
"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)
}
}
与TCP RPC相比就是将rpc.ServeConn(conn)改为jsonrpc.ServeConn(conn)。
客户端示例代码:
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)
}
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)
}
其变化也是将以前的rpc.Dial()改为jsonrpc.Dial()。