rpc
包提供通过网络或其他I/0
连接的对象的导出方法的访问。服务器注册一个对象,使其作为具有对象类型名称的可见服务。注册后,可以远程访问对象的导出方法。服务器可以注册不同类型的多个对象服务,但是注册相同类型的多个对象是错误的。而且对象上的方法只有满足下面的条件时,远程调用才能可用,否则方法会被忽略。
方法看起来像下面结构:
func (t *T) MethodName(argType T1, replyType *T2) error
T1
和T2
可以通过encoding/gob
进行编码。方法的第一个参数代表调用者提供的参数,第二个参数代表返回给调用者的结果参数。方法返回的值如果不为空,那么返回reply
参数将不会返回给客户端。
Register
或者RegisterName
去注册一个rpc
服务。Listen
方法直接监听本地网络上的地址,官方给的例子中在监听之前调用了rpc.HandleHTTP
方法。// rpc.HandleHTTP()
func HandleHTTP() {
DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
}
const (
DefaultRPCPath = "/_goRPC_"
DefaultDebug = "/debug/rpc"
)
func (server *Server) HandleHTTP(rpcPath, debugPath string) {
http.Handle(rpcPath, server)
http.Handle(debugPath, debugHTTP{server})
}
注册了两个handler
,可以查看服务调用的情况。例如用Listen
监听localhost:1234
网络,那么通过路由localhost:1234/debug/rpc
,可以看到如下的内容:
http.Serve
方法用来接收即将到来的HTTP
连接,它会为每个连接创建一个新的服务goroutine
。每个服务将会读取请求,然后调用handler
来回复他们。handler
通常是nil
,这时候DefaultServeMux
将会被使用。func Serve(l net.Listener, handler Handler) error {
srv := &Server{Handler: handler}
return srv.Serve(l)
}
func (srv *Server) Serve(l net.Listener) error {
// ...
for {
rw, e := l.Accept()
if e != nil {
// ...
}
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
go c.serve(ctx)
}
}
上面的http.Serve
方法也可以换作其他的方法处理,例如使用Accept
接口和ServeConn
方法以及for
语句来实现持续处理请求。
在第二步中,Listen
方法返回一个listener
监听对象,然后在其上调用Accept
方法。成功调用后,会返回一个可用连接。然后调用rpc.ServeConn
方法在这个连接上运行默认的服务。ServeConn
会阻塞,直到客户端挂起。通常使用go
并发来执行ServeConn
,ServeConn
方法使用gob
格式的codec
,如果需要替换,用ServeCodec
方法。rpc
包提供两种codec
,一种是gob
,一种是json
。
// ...
listener, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("failed to listen tcp, error:", err)
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("failed to accept connection, error:", err)
}
go rpc.ServeConn(conn)
}
package main
import (
"errors"
"fmt"
"net"
"net/http"
"net/rpc"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error {
if args.B == 0 {
return errors.New("divide by zero")
}
quo.Quo = args.A / args.B
quo.Rem = args.A % args.B
return nil
}
func main() {
arith := new(Arith)
err := rpc.Register(arith)
if err != nil {
fmt.Println("failed to register rpc service. error: ", err)
return
}
rpc.HandleHTTP()
l, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("listen error:", err)
}
if err = http.Serve(l, nil); err != nil {
fmt.Println("failed to accept incoming request. error: ", err)
return
}
}
Dial
方法来连接特定网络地址上的RPC
服务。连接完成后,会生成一个新的客户端。也可以使用DialHTTP
方法,DialHTTP
会连接特定网络地址上的HTTP RPC
服务,监听默认HTTP RPC
路径。func DialHTTP(network, address string) (*Client, error) {
return DialHTTPPath(network, address, DefaultRPCPath)
}
func DialHTTPPath(network, address, path string) (*Client, error) {
var err error
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
// ...
}
Call
接口,Call
接口会调用指定的RPC
服务,等待完成后,返回错误状态。Call
接口是同步调用RPC
服务,如果需要异步调用,我们可以在客户端上调用Go
接口。func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
// ...
}
调用如果没有出错,那么上述返回的Call
中的Done
通道会传入一个没有错误的Call
。可以根据是否有错来判断是否成功调用。例如:
// ...
quotient := new(Quotient)
divCall := client.Go("Arith.Divide", args, quotient, nil)
replyCall := <-divCall.Done
if replyCall.Error != nil {
fmt.Println("failed to call function asynchronous, error:", replyCall.Error)
}
fmt.Printf("Arith: %d/%d=%d...%d", args.A, args.B, quotient.Quo, quotient.Rem)
package main
import (
"fmt"
"net/rpc"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
fmt.Println("failed to dial http rpc server, error:", err)
}
// Synchronous call
args := &Args{7, 8}
var reply int
if err = client.Call("Arith.Multiply", args, &reply); err != nil {
fmt.Println("failed to call function synchronously, error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
// Asynchronous call
quotient := new(Quotient)
divCall := client.Go("Arith.Divide", args, quotient, nil)
replyCall := <-divCall.Done
if replyCall.Error != nil {
fmt.Println("failed to call function asynchronous, error:", replyCall.Error)
}
fmt.Printf("Arith: %d/%d=%d...%d", args.A, args.B, quotient.Quo, quotient.Rem)
}