mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 02:35:53 +08:00
add chapter 15.9 (#686)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 `encoding` 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 "pickle" 和 Java 的 "Serialization"。
|
Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 `encoding` 包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写)。类似于 Python 的 "pickle" 和 Java 的 "Serialization"。
|
||||||
|
|
||||||
Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。
|
Gob 通常用于远程方法调用(RPCs,参见 [15.9节](15.9.md) 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。
|
||||||
它和 JSON 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。
|
它和 JSON 或 XML 有什么不同呢?Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。
|
||||||
Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。
|
Gob 不是可外部定义,语言无关的编码方式。因此它的首选格式是二进制,而不是像 JSON 和 XML 那样的文本格式。
|
||||||
Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。
|
Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。
|
||||||
|
117
eBook/15.9.md
Normal file
117
eBook/15.9.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# 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 章看到一个完整的项目,它是一个使用了 `rpc` 的应用程序。
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- [目录](directory.md)
|
||||||
|
- 上一节:[精巧的多功能网页服务器](15.8.md)
|
||||||
|
- 下一节:[基于网络的通道 netchan](15.10.md)
|
39
eBook/examples/chapter_15/rpc/rpc_client.go
Normal file
39
eBook/examples/chapter_15/rpc/rpc_client.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// rpc_client.go
|
||||||
|
// if the server is not started:
|
||||||
|
// can't get the server to start, so client stops immediately with error:
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
/* Output:
|
||||||
|
Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_client.exe ...
|
||||||
|
|
||||||
|
Args: 7 * 8 = 56
|
||||||
|
End Process exit status 0
|
||||||
|
*/
|
13
eBook/examples/chapter_15/rpc/rpc_objects.go
Normal file
13
eBook/examples/chapter_15/rpc/rpc_objects.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// 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
|
||||||
|
}
|
32
eBook/examples/chapter_15/rpc/rpc_server.go
Normal file
32
eBook/examples/chapter_15/rpc/rpc_server.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// rpc_server.go
|
||||||
|
// after client-exits the server shows the message:
|
||||||
|
// 1:1234: The specified network name is no longer available.
|
||||||
|
// 2011/08/01 16:19:04 rpc: rpc: server cannot decode request: WSARecv tcp 127.0.0.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
/* Output:
|
||||||
|
Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...
|
||||||
|
|
||||||
|
** after 5 s: **
|
||||||
|
End Process exit status 0
|
||||||
|
*/
|
39
eBook/examples/chapter_15/rpc_updated/rpc_client.go
Normal file
39
eBook/examples/chapter_15/rpc_updated/rpc_client.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// rpc_client.go
|
||||||
|
// if the server is not started:
|
||||||
|
// can't get the server to start, so client stops immediately with error:
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
/* Output:
|
||||||
|
Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_client.exe ...
|
||||||
|
|
||||||
|
Args: 7 * 8 = 56
|
||||||
|
End Process exit status 0
|
||||||
|
*/
|
@@ -0,0 +1,11 @@
|
|||||||
|
// rpc_objects.go
|
||||||
|
package rpc_objects
|
||||||
|
|
||||||
|
type Args struct {
|
||||||
|
N, M int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Args) Multiply(args *Args, reply *int) error {
|
||||||
|
*reply = args.N * args.M
|
||||||
|
return nil
|
||||||
|
}
|
32
eBook/examples/chapter_15/rpc_updated/rpc_server.go
Normal file
32
eBook/examples/chapter_15/rpc_updated/rpc_server.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// rpc_server.go
|
||||||
|
// after client-exits the server shows the message:
|
||||||
|
// 1:1234: The specified network name is no longer available.
|
||||||
|
// 2011/08/01 16:19:04 rpc: rpc: server cannot decode request: WSARecv tcp 127.0.0.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
/* Output:
|
||||||
|
Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...
|
||||||
|
|
||||||
|
** after 5 s: **
|
||||||
|
End Process exit status 0
|
||||||
|
*/
|
Reference in New Issue
Block a user