15.9 用 rpc 实现远程过程调用

Go 程序之间可以使用 net/rpc 包实现相互通信,这是另一种客户端-服务器应用场景。它提供了一种方便的途径,通过网络连接调用远程函数。当然,仅当程序运行在不同机器上时,这项技术才实用。rpc 包建立在 gob 包之上(见 12.11 节),实现了自动编码/解码传输的跨网络方法调用。

服务器端需要注册一个对象实例,与其类型名一起,使之成为一项可见的服务:它允许远程客户端跨越网络或其他 I/O 连接访问此对象已导出的方法。总之就是在网络上暴露类型的方法。

rpc 包使用了 http 和 tcp 协议,以及用于数据传输的 gob 包。服务器端可以注册多个不同类型的对象(服务),但同一类型的多个对象会产生错误。

我们讨论一个简单的例子:定义一个类型 Args 及其方法 Multiply(),完美地置于单独的包中。方法必须返回可能的错误。

示例15.21 rpc_objects.go

  1. package rpc_objects
  2. import "net"
  3. type Args struct {
  4. N, M int
  5. }
  6. func (t *Args) Multiply(args *Args, reply *int) net.Error {
  7. *reply = args.N * args.M
  8. return nil
  9. }

译注:Go 当前版本要求此方法返回类型为 error,以上示例中返回 net.Error 已无法通过编译,见更新后的 rpc_objects.go

服务器端产生一个 rpc_objects.Args 类型的对象 calc,并用 rpc.Register(object) 注册。调用 HandleHTTP(),然后用 net.Listen 在指定的地址上启动监听。也可以按名称来注册对象,例如:rpc.RegisterName("Calculator", calc)

以协程启动 http.Serve(listener, nil) 后,会为每一个进入 listener 的 HTTP 连接创建新的服务线程。我们必须用诸如 time.Sleep(1000e9) 来使服务器在一段时间内保持运行状态。

示例 15.22 rpc_server.go

  1. package main
  2. import (
  3. "net/http"
  4. "log"
  5. "net"
  6. "net/rpc"
  7. "time"
  8. "./rpc_objects"
  9. )
  10. func main() {
  11. calc := new(rpc_objects.Args)
  12. rpc.Register(calc)
  13. rpc.HandleHTTP()
  14. listener, e := net.Listen("tcp", "localhost:1234")
  15. if e != nil {
  16. log.Fatal("Starting RPC-server -listen error:", e)
  17. }
  18. go http.Serve(listener, nil)
  19. time.Sleep(1000e9)
  20. }

输出:

  1. Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...
  2. ** 5 秒后: **
  3. End Process exit status 0

客户端必须知晓对象类型及其方法的定义。执行 rpc.DialHTTP() 连接到服务器后,就可以用 client.Call("Type.Method", args, &reply) 调用远程对象的方法。Type 是远程对象的类型名,Method 是要调用的方法,args 是用 Args 类型初始化的对象,reply 是一个必须事先声明的变量,方法调用产生的结果将存入其中。

示例 15.23 rpc_client.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/rpc"
  6. "./rpc_objects"
  7. )
  8. const serverAddress = "localhost"
  9. func main() {
  10. client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
  11. if err != nil {
  12. log.Fatal("Error dialing:", err)
  13. }
  14. // Synchronous call
  15. args := &rpc_objects.Args{7, 8}
  16. var reply int
  17. err = client.Call("Args.Multiply", args, &reply)
  18. if err != nil {
  19. log.Fatal("Args error:", err)
  20. }
  21. fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)
  22. }

先启动服务器,再运行客户端,然后就能得到如下输出结果:

  1. Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_client.exe ...
  2. Args: 7 * 8 = 56
  3. End Process exit status 0

该远程调用以同步方式进行,它会等待服务器返回结果。也可使用如下方式异步地执行调用:

  1. call1 := client.Go("Args.Multiply", args, &reply, nil)
  2. replyCall := <- call1.Done

如果最后一个参数值为 nil ,调用完成后会创建一个新的通道。

如果你有一个以 root 管理员身份运行的 Go 服务器,想要以不同的用户身份运行某部分代码,Brad Fitz 利用 rpc 写的 go-runas 包可以完成任务:https://github.com/bradfitz/go-runas。我们将会在 19 章看到一个完整的项目,它是一个使用了 rpc 的应用程序。

链接