RPC
简介
RPC(Remote Procedure Call,远程过程调用)允许一台计算机通过网络从远程计算机的程序中请求服务。RPC协议构建于TCP或UDP,或者是HTTP上。允许开发者直接调用另一台服务器上的程序,而开发者无需另外的为这个调用过程编写网络通信相关代码。
RPC允许跨机器、跨语言调用计算机程序方法。
工作原理
运行时,一次客户机对服务器的 RPC 调用,其内部操作大致有如下:
- 调用客户端句柄:执行传送参数
- 调用本地系统内核发送网络消息
- 消息传送到远程主机
- 服务器句柄得到消息并取得参数
- 执行远程过程
- 执行的过程将结果返回服务器句柄
- 服务器句柄返回结果,调用远程系统内核
- 消息传回本地主机
- 客户句柄由内核接收消息
- 客户接收句柄返回的数据
RPC vs RESTful
RPC 可以通过 TCP、UDP 或者 HTTP 等进行传输消息,称为 RPC over TCP、RPC over HTTP。
首先比较 RPC over HTTP 和 RESTful:
- RPC 的客户端和服务器是紧密耦合的,,客户端需要知道调用的过程的名字,过程的参数以及它们的类型、顺序等。一旦服务器更改了过程的实现, 客户端的实现很容易出问题。RESTful 的客户端和服务器是松散的关系,客户端并不关心服务器端的具体实现,比较灵活。
- 它们操作的对象不一样。RPC 操作的是方法对象,而 RESTful 操作的是资源。
- RESTful 执行的是对资源的增删改查(CRUD),如果实现一个其他具体操作 RPC 的实现更加具有意义。
接着比较 RPC over TCP 和 RESTful:
- RPC over TCP 减少了 HTTP 协议带来的额外开销(如HTTP 请求头)。
- RPC over TCP 可以通过长连接减少连接的建立所产生的花费,当然 RESTful 也可以通过 keep-alive 实现长连接,但是它最大的一个问题是它的 request-response 模型是阻塞的(HTTP 1.0 和 HTTP 1.1 是阻塞的,HTTP 2.0 不是),发送一个请求得到响应后才能发送第二个,而 RPC 没有这个限制。
Go RPC
GO语言官方提供了net/rpc
包实现RPC,使用encoding/gob
进行编解码,支持TCP或者HTTP数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc
实现的RPC方法没办法实现跨语言调用。
net/rpc 介绍
net/rpc
包允许PRC客户端程序通过网络或者其他IO连接,调用一个远程对象的公开方法。在PRC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个RPC服务端可以注册多个不通类型的对象,但不允许注册同一类型的多个对象。
只有满足如下标准的方法才能用于远程访问,其余方法会被忽略:
- 方法是导出的(首字母大写)
- 方法有两个参数,第一个参数是接收的参数,第二个参数是返回给客户端的参数
- 方法的第二个参数是指针
- 方法只有一个error接口类型的返回值
事实上,方法必须看起来像这样:
1
| func (t *T) MethodName(argType T1, replyType *T2) error
|
方法的第一个参数代表调用者提供的参数;第二个参数代表返回给调用者的参数。方法的返回值,如果非nil,将被作为字符串回传,在客户端看来就和errors.New
创建的一样。如果返回了错误,回复的参数将不会被发送给客户端。
net/rpc/jsonrpc 介绍
官方还提供了net/rpc/jsonrpc
实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc
库是基于TCP协议实现的,暂时不支持使用http进行数据传输
案例
net/rpc
下面利用HTTP作为RPC载体,通过net/http
监听客户端的连接请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package main
import ( "errors" "fmt" "log" "net" "net/http" "net/rpc" )
type Arith struct { }
type ArithRequest struct { A int B int }
type ArithResponse struct { Pro int Quo int Rem int }
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error { res.Pro = req.A * req.B return nil }
func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error { if req.B == 0 { return errors.New("divide by zero") } res.Quo = req.A / req.B res.Rem = req.A % req.B return nil }
func main() { rpc.Register(new(Arith)) rpc.HandleHTTP()
lis, err := net.Listen("tcp", "127.0.0.1:8888") if err != nil { log.Fatalln("Listen error:", err) }
fmt.Println("start connection...")
http.Serve(lis, nil) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package main
import ( "fmt" "log" "net/rpc" )
type ArithRequest struct { A int B int }
type ArithResponse struct { Pro int Quo int Rem int }
func main() { conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8888") if err != nil { log.Fatalln("Dial error:", err) }
req := ArithRequest{10, 3} var res ArithResponse
err = conn.Call("Arith.Multiply", req, &res) if err != nil { log.Fatalln("Multiply error:", err) } fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
err = conn.Call("Arith.Divide", req, &res) if err != nil { log.Fatalln("Divide error:", err) } fmt.Printf("%d / %d = %d ... %d\n", req.A, req.B, res.Quo, res.Rem)
}
|
net/rpc/jsonrpc
下面演示net/rpc/jsonrpc
的使用方法,此方法只支持TCP作为载体,但是可以跨语言调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| package main
import ( "errors" "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" "os" )
type Arith struct { }
type ArithRequest struct { A int B int }
type ArithResponse struct { Pro int Quo int Rem int }
func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error { res.Pro = req.A * req.B return nil }
func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error { if req.B == 0 { return errors.New("divide by zero") } res.Quo = req.A / req.B res.Rem = req.A % req.B return nil }
func main() { rpc.Register(new(Arith))
lis, err := net.Listen("tcp", "127.0.0.1:6666") if err != nil { log.Fatalln("Listen error:", err) }
fmt.Println("start connection...")
for { conn, err := lis.Accept() if err != nil { continue }
go func(conn net.Conn) { fmt.Fprintf(os.Stdout, "%s", "new client in coming\n") jsonrpc.ServeConn(conn) }(conn) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package main
import ( "fmt" "log" "net/rpc/jsonrpc" )
type Arith struct { }
type ArithRequest struct { A int B int }
type ArithResponse struct { Pro int Quo int Rem int }
func main() { conn, err := jsonrpc.Dial("tcp", "127.0.0.1:6666") if err != nil { log.Fatalln("Dial error:", err) }
req := ArithRequest{10, 3} var res ArithResponse
err = conn.Call("Arith.Multiply", req, &res) if err != nil { log.Fatalln("Multiply error:", err) } fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
err = conn.Call("Arith.Divide", req, &res) if err != nil { log.Fatalln("Divide error:", err) } fmt.Printf("%d / %d = %d ... %d\n", req.A, req.B, res.Quo, res.Rem)
}
|