15.9 用 rpc 实现远程过程调用

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

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

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

    示例15.21

    服务器端产生一个 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. "./rpc_objects"
    8. )
    9. func main() {
    10. calc := new(rpc_objects.Args)
    11. rpc.Register(calc)
    12. rpc.HandleHTTP()
    13. listener, e := net.Listen("tcp", "localhost:1234")
    14. log.Fatal("Starting RPC-server -listen error:", e)
    15. }
    16. go http.Serve(listener, nil)
    17. time.Sleep(1000e9)
    18. }

    输出:

    示例 15.23

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

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

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

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

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