Go 程序可以通过 net/rpc 包相互通讯,所以这是另一个客户端 - 服务器端模式的应用。它提供了通过网络连接进行函数调用的便捷方法。只有程序运行在不同的机器上它才有用。rpc
包建立在 gob
( 参见 12.11 章节 ) 上,将其编码 / 解码,自动转换成可以通过网络调用的方法。
服务器注册一个对象,通过对象的类型名称暴露这个服务:注册后就可以通过网络或者其他远程客户端的 I/O 连接它的导出方法。这是关于通过网络暴露类型上的方法。
这个包使用了 http 协议、tcp 协议和用于数据传输的 gob 包。服务器可以注册多个不同类型的对象(服务),但是相同的类型注册多个对象的时候会出错。
这里我们讨论一个简单的示例: 我们定义一个 Args
类型,并且在它上面创建一个 Multiply
方法,最好封装在一个单独的包中;这个方法必须返回一个可能的错误。
示例 15.21—rpc_objects.go:
// rpc_objects.go
package rpc_objects
import "net"
type Args struct {
N, M int
}
func (t *Args) Multiply(args *Args, reply *int) net.Error {
*reply = args.N * args.M
return 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 main
import (
"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 main
import (
"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 call
args := &rpc_objects.Args{7, 8}
var reply int
err = 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 = 56
End 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
应用程序。