Go 程序可以通过 net/rpc 包相互通讯,所以这是另一个客户端 - 服务器端模式的应用。它提供了通过网络连接进行函数调用的便捷方法。只有程序运行在不同的机器上它才有用。rpc 包建立在 gob ( 参见 12.11 章节 ) 上,将其编码 / 解码,自动转换成可以通过网络调用的方法。
服务器注册一个对象,通过对象的类型名称暴露这个服务:注册后就可以通过网络或者其他远程客户端的 I/O 连接它的导出方法。这是关于通过网络暴露类型上的方法。
这个包使用了 http 协议、tcp 协议和用于数据传输的 gob 包。服务器可以注册多个不同类型的对象(服务),但是相同的类型注册多个对象的时候会出错。
这里我们讨论一个简单的示例: 我们定义一个 Args 类型,并且在它上面创建一个 Multiply 方法,最好封装在一个单独的包中;这个方法必须返回一个可能的错误。
示例 15.21—rpc_objects.go:
// rpc_objects.gopackage rpc_objectsimport "net"type Args struct {N, M int}func (t *Args) Multiply(args *Args, reply *int) net.Error {*reply = args.N * args.Mreturn nil}
服务器创建一个用于计算的对象,并且将它通过 rpc.Register(object) 注册,调用 HandleHTTP() ,并在一个地址上使用 net.Listen 开始监听。你也可以通过名称注册对象,如:rpc.RegisterName("Calculator", calc)
译者注:
rpc.Register要求Multiply方法的返回值要求是一个 error 类型,所以示例的 net.Error 执行会出错,因此要换成 error 类型(有可能是版本更新造成的,测试使用的 Go 版本为: go1.10.1 )。
对每一个进入到 listener 的请求,都是由协程去启动一个 http.Serve(listener, nil) ,为每一个传入的 HTTP 连接创建一个新的服务线程。我们必须保证在一个特定的时间内服务器是唤醒状态,例如:time.Sleep(1000e9) (1000 秒)
示例 15.22—rpc_server.go:
// rpc_server.go// 原文注释已经被删除,因为和此代码没有关系,个人猜测是作者在这个示例修改之前的代码。package mainimport ("net/http""log""net""net/rpc""time""./rpc_objects")func main() {calc := new(rpc_objects.Args)rpc.Register(calc)rpc.HandleHTTP()listener, e := net.Listen("tcp", "localhost:1234")if e != nil {log.Fatal("Starting RPC-server -listen error:", e)}go http.Serve(listener, nil)time.Sleep(1000e9)}/* 输出:启动程序 E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...** after 5 s: **End Process exit status 0*/
客户端必须知道服务器端定义的对象的类型和它的方法。它调用 rpc.DialHTTP() 去创建连接的客户端,当客户端被创建时,它可以通过 client.Call("Type. Method", args, &reply) 去调用远程的方法,其中 Type 与 Method 是调用的远程服务器端被定义的类型和方法, args 是一个类型的初始化对象,reply 是一个变量,使用前必须要先声明它,它用来存储调用方法的返回结果。
示例 15.23—rpc_client.go:
// rpc_client.go// 如果服务器端没有启动:// 不能启动服务, 所以客户端会立刻停止并报错:// 2011/08/01 16:08:05 Error dialing:dial tcp :1234:// The requested address is not valid in its context.// with serverAddress = localhost:// 2011/08/01 16:09:23 Error dialing:dial tcp 127.0.0.1:1234:// No connection could be made because the target machine actively refused it.package mainimport ("fmt""log""net/rpc""./rpc_objects")const serverAddress = "localhost"func main() {client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")if err != nil {log.Fatal("Error dialing:", err)}// Synchronous callargs := &rpc_objects.Args{7, 8}var reply interr = client.Call("Args.Multiply", args, &reply)if err != nil {log.Fatal("Args error:", err)}fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)}/* 输出结果:Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_client.exe ...Args: 7 * 8 = 56End Process exit status 0*/
这个调用是同步的,所以需要等待结果返回。如果想要异步调用可以这样:
call1 := client.Go("Args.Multiply", args, &reply, nil)replyCall := <- call1.Done
如果最后一个参数的值为 nil,在调用完成后将分配一个新的通道。
如果你有一台用 root 运行的 Go 服务器,并且想以不同的用户去运行你的代码,Brad Fitz 的 go-runas 包使用 rpc 包可以实现:github.com/bradfitz/go-runas 。在第 19 章节 我们将看到在一个完整的项目中的 rpc 应用程序。
