15.9. RPC 远程调用

未匹配的标注

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) 去调用远程的方法,其中 TypeMethod 是调用的远程服务器端被定义的类型和方法, 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 应用程序。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/the-way-to-go/1...

译文地址:https://learnku.com/docs/the-way-to-go/1...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~