Files
the-way-to-go_ZH_CN/eBook/15.9.md
Haigang Zhou 72f2eccbc5 第十五章修改 (#834)
Co-authored-by: Joe Chen <jc@unknwon.io>
2022-05-17 16:33:20 +08:00

118 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 15.9 用 rpc 实现远程过程调用
Go 程序之间可以使用 `net/rpc` 包实现相互通信,这是另一种客户端-服务器应用场景。它提供了一种方便的途径,通过网络连接调用远程函数。当然,仅当程序运行在不同机器上时,这项技术才实用。`rpc` 包建立在 `gob` 包之上(见 [12.11 节](12.11.md)),实现了自动编码/解码传输的跨网络方法调用。
服务器端需要注册一个对象实例,与其类型名一起,使之成为一项可见的服务:它允许远程客户端跨越网络或其他 I/O 连接访问此对象已导出的方法。总之就是在网络上暴露类型的方法。
`rpc` 包使用了 http 和 tcp 协议,以及用于数据传输的 `gob` 包。服务器端可以注册多个不同类型的对象(服务),但同一类型的多个对象会产生错误。
我们讨论一个简单的例子:定义一个类型 `Args` 及其方法 `Multiply()`,完美地置于单独的包中。方法必须返回可能的错误。
示例15.21 [rpc_objects.go](examples/chapter_15/rpc/rpc_objects.go)
```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
}
```
**译注Go 当前版本要求此方法返回类型为 `error`,以上示例中返回 `net.Error` 已无法通过编译,见更新后的 [rpc_objects.go](examples/chapter_15/rpc_updated/rpc_objects/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](examples/chapter_15/rpc/rpc_server.go)
```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)
}
```
输出:
Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...
** 5 秒后: **
End Process exit status 0
客户端必须知晓对象类型及其方法的定义。执行 `rpc.DialHTTP()` 连接到服务器后,就可以用 `client.Call("Type.Method", args, &reply)` 调用远程对象的方法。`Type` 是远程对象的类型名,`Method` 是要调用的方法,`args` 是用 `Args` 类型初始化的对象,`reply` 是一个必须事先声明的变量,方法调用产生的结果将存入其中。
示例 15.23 [rpc_client.go](examples/chapter_15/rpc/rpc_client.go)
```go
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
该远程调用以同步方式进行,它会等待服务器返回结果。也可使用如下方式异步地执行调用:
```go
call1 := client.Go("Args.Multiply", args, &reply, nil)
replyCall := <- call1.Done
```
如果最后一个参数值为 `nil` ,调用完成后会创建一个新的通道。
如果你有一个以 root 管理员身份运行的 Go 服务器想要以不同的用户身份运行某部分代码Brad Fitz 利用 `rpc` 写的 `go-runas` 包可以完成任务:[https://github.com/bradfitz/go-runas](https://github.com/bradfitz/go-runas)。我们将会在 [19 章](19.0.md)看到一个完整的项目,它是一个使用了 `rpc` 的应用程序。
## 链接
- [目录](directory.md)
- 上一节:[精巧的多功能网页服务器](15.8.md)
- 下一节:[基于网络的通道 netchan](15.10.md)