golang rpc包使用介绍

rpc包提供通过网络或其他I/0连接的对象的导出方法的访问。服务器注册一个对象,使其作为具有对象类型名称的可见服务。注册后,可以远程访问对象的导出方法。服务器可以注册不同类型的多个对象服务,但是注册相同类型的多个对象是错误的。而且对象上的方法只有满足下面的条件时,远程调用才能可用,否则方法会被忽略。

  • 方法的类型是可导出的
  • 方法是导出的
  • 方法有两个参数,都是可导出的
  • 第二个参数是指针类型
  • 方法返回一个error类型

方法看起来像下面结构:

func (t *T) MethodName(argType T1, replyType *T2) error

T1T2可以通过encoding/gob进行编码。方法的第一个参数代表调用者提供的参数,第二个参数代表返回给调用者的结果参数。方法返回的值如果不为空,那么返回reply参数将不会返回给客户端。

示例

服务端处理过程

  1. 首先使用Register或者RegisterName去注册一个rpc服务。
  2. 接着调用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,可以看到如下的内容:

  1. 接着使用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并发来执行ServeConnServeConn方法使用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
	}
}

客户端处理过程

  1. 使用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
    }
    // ...
}
  1. 在第一步创建的客户端上调用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)
}

你可能感兴趣的:(RPC)