Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go net/rpc 包详解

概述

net/rpc 包提供了通过网络或其他 I/O 连接访问对象的导出方法的功能。该包允许服务器注册一个对象,使其作为服务对外提供,客户端可以像调用本地方法一样调用远程方法。

重要说明

  • ✓ 支持远程过程调用(RPC)
  • ✓ 使用 encoding/gob 编码数据(默认)
  • ✓ 支持 TCP 和 HTTP 传输
  • ✓ 支持同步和异步调用
  • ✓ 支持自定义编解码器
  • ✓ Go 1.0+ 引入,已冻结(不再接受新特性)
  • ✓ 仅支持 Go 语言之间的通信

RPC 方法要求: 只有满足以下条件的方法才能被远程访问:

  • 方法的类型是导出的(首字母大写)
  • 方法是导出的(首字母大写)
  • 方法有两个参数,都是导出类型或内建类型
  • 方法的第二个参数是指针
  • 方法只有一个 error 接口类型的返回值

方法签名

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

其中 T1 和 T2 必须能被 encoding/gob 编解码。

包导入

import (
    "net/rpc"
)

基本使用

1. 服务器端示例

package main

import (
    "errors"
    "fmt"
    "net"
    "net/http"
    "net/rpc"
    "log"
)

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)
    rpc.Register(arith)
    rpc.HandleHTTP()
    
    l, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("listen error:", err)
    }
    
    fmt.Println("RPC server listening on :1234")
    http.Serve(l, nil)
}

2. 客户端示例

package main

import (
    "fmt"
    "net/rpc"
    "log"
)

type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

func main() {
    client, err := rpc.DialHTTP("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    defer client.Close()
    
    // 同步调用
    args := &Args{7, 8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal("arith error:", err)
    }
    fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
    
    // 异步调用
    quotient := new(Quotient)
    divCall := client.Go("Arith.Divide", args, quotient, nil)
    replyCall := <-divCall.Done
    if replyCall.Error != nil {
        log.Fatal("divide error:", replyCall.Error)
    }
    fmt.Printf("Arith: %d/%d=%d remainder %d\n", 
        args.A, args.B, quotient.Quo, quotient.Rem)
}

运行结果:

Arith: 7*8=56
Arith: 7/8=0 remainder 7

一、常量

DefaultRPCPath

const DefaultRPCPath = "/_goRPC_"

HandleHTTP 使用的默认 RPC 路径。

DefaultDebugPath

const DefaultDebugPath = "/debug/rpc"

HandleHTTP 使用的默认调试路径。


二、变量

DefaultServer

var DefaultServer = NewServer()

DefaultServer 是 *Server 的默认实例。本包中与 Server 方法同名的函数都是对其方法的封装。


三、函数(按 a-z 排序)

Accept

func Accept(lis net.Listener)

Accept 在监听器上接受连接,并为每个传入的连接提供服务到 DefaultServer。Accept 会阻塞;调用者通常在 go 语句中调用它。

示例:

lis, err := net.Listen("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
go rpc.Accept(lis) // 在后台运行

HandleHTTP

func HandleHTTP()

HandleHTTP 在 DefaultRPCPath 上为 DefaultServer 注册 HTTP 处理器,在 DefaultDebugPath 上注册调试处理器。仍然需要调用 http.Serve(),通常在 go 语句中。

示例:

rpc.HandleHTTP()
l, err := net.Listen("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
go http.Serve(l, nil)

Register

func Register(rcvr any) error

Register 在 DefaultServer 中注册接收者的方法。

参数:

  • rcvr - 要注册的对象

返回值:

  • error - 如果接收者不是导出类型或没有合适的方法则返回错误

示例:

type Calculator struct{}

func (c *Calculator) Add(args *Args, reply *int) error {
    *reply = args.A + args.B
    return nil
}

calc := new(Calculator)
err := rpc.Register(calc)
if err != nil {
    log.Fatal(err)
}

RegisterName

func RegisterName(name string, rcvr any) error

RegisterName 类似于 Register,但使用提供的名称代替接收者的具体类型名作为服务名。

参数:

  • name - 服务名称
  • rcvr - 要注册的对象

示例:

arith := new(Arith)
err := rpc.RegisterName("MyArithmetic", arith)
// 客户端调用:"MyArithmetic.Multiply"

ServeCodec

func ServeCodec(codec ServerCodec)

ServeCodec 类似于 ServeConn,但使用指定的编解码器来解码请求和编码响应。

示例:

conn, err := net.Dial("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
codec := jsonrpc.NewServerCodec(conn)
rpc.ServeCodec(codec)

ServeConn

func ServeConn(conn io.ReadWriteCloser)

ServeConn 在单个连接上运行 DefaultServer。ServeConn 会阻塞,直到客户端挂断。调用者通常在 go 语句中调用 ServeConn。ServeConn 在连接上使用 gob 线格式。要使用替代编解码器,请使用 ServeCodec。

示例:

conn, err := net.Dial("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
go rpc.ServeConn(conn)

ServeRequest

func ServeRequest(codec ServerCodec) error

ServeRequest 类似于 ServeCodec,但同步服务单个请求。它不会在完成后关闭编解码器。


四、类型(按 a-z 排序)

Call

Call 表示一个活动的 RPC 调用。

type Call struct {
    ServiceMethod string      // 服务和方法的名称
    Args          any         // 参数
    Reply         any         // 回复
    Error         error       // 错误
    Done          chan *Call  // 完成信号
}

字段说明:

  • ServiceMethod - 格式:“Service.Method”
  • Args - 调用参数
  • Reply - 返回参数
  • Error - 调用错误
  • Done - 调用完成时发送信号的通道

Client

Client 表示一个 RPC 客户端。单个客户端可能有多个未完成的调用,并且可以被多个 goroutine 同时使用。

type Client struct {
    // 内含隐藏或非导出字段
}

Client.Call

func (client *Client) Call(serviceMethod string, args any, reply any) error

Call 调用指定的方法,等待远程调用完成,并返回错误状态。

参数:

  • serviceMethod - 服务和方法名,格式:“Service.Method”
  • args - 参数指针
  • reply - 接收返回值的指针

返回值:

  • error - 调用错误

示例:

var reply int
err := client.Call("Arith.Multiply", &Args{7, 8}, &reply)
if err != nil {
    log.Fatal(err)
}

Client.Close

func (client *Client) Close() error

Close 调用底层编解码器的 Close 方法。如果连接已经在关闭中,则返回 ErrShutdown。

示例:

defer client.Close()

Client.Go

func (client *Client) Go(serviceMethod string, args any, reply any, done chan *Call) *Call

Go 异步调用函数。它返回表示调用的 Call 结构。done 通道将在调用完成时通过返回相同的 Call 对象来发出信号。如果 done 为 nil,Go 将分配一个新通道。如果非 nil,done 必须是缓冲的,否则 Go 会故意崩溃。

参数:

  • serviceMethod - 服务和方法名
  • args - 参数指针
  • reply - 接收返回值的指针
  • done - 完成通道(可为 nil)

返回值:

  • *Call - 调用对象

示例:

// 异步调用
quotient := new(Quotient)
call := client.Go("Arith.Divide", &Args{10, 3}, quotient, nil)
replyCall := <-call.Done
if replyCall.Error != nil {
    log.Fatal(replyCall.Error)
}

ClientCodec

ClientCodec 实现了 RPC 会话客户端侧的 RPC 请求写入和 RPC 响应读取。

type ClientCodec interface {
    WriteRequest(*Request, any) error
    ReadResponseHeader(*Response) error
    ReadResponseBody(any) error
    Close() error
}

方法说明:

  • WriteRequest - 写入 RPC 请求
  • ReadResponseHeader - 读取响应头
  • ReadResponseBody - 读取响应体
  • Close - 关闭编解码器

Request

Request 是在每个 RPC 调用之前写入的头部。它在内部使用,但在此处记录以帮助调试。

type Request struct {
    ServiceMethod string
    Seq           uint64
}

Response

Response 是在每个 RPC 返回之前写入的头部。它在内部使用,但在此处记录以帮助调试。

type Response struct {
    ServiceMethod string
    Seq           uint64
    Error         string
}

Server

Server 表示一个 RPC 服务器。

type Server struct {
    // 内含隐藏或非导出字段
}

Server.Accept

func (server *Server) Accept(lis net.Listener)

Accept 在监听器上接受连接,并为每个传入的连接提供服务。Accept 会阻塞直到监听器返回非 nil 错误。调用者通常在 go 语句中调用 Accept。

示例:

server := rpc.NewServer()
lis, err := net.Listen("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
go server.Accept(lis)

Server.HandleHTTP

func (server *Server) HandleHTTP(rpcPath, debugPath string)

HandleHTTP 在 rpcPath 上为 RPC 消息注册 HTTP 处理器,在 debugPath 上注册调试处理器。仍然需要调用 http.Serve(),通常在 go 语句中。

示例:

server := rpc.NewServer()
server.HandleHTTP(rpc.DefaultRPCPath, rpc.DefaultDebugPath)

Server.Register

func (server *Server) Register(rcvr any) error

Register 在服务器中发布接收者值的方法集,这些方法满足以下条件:

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

如果接收者不是导出类型或没有合适的方法,则返回错误。它还会使用 log 包记录错误。客户端使用“Type.Method“格式的字符串访问每个方法,其中 Type 是接收者的具体类型。

Server.RegisterName

func (server *Server) RegisterName(name string, rcvr any) error

RegisterName 类似于 Register,但使用提供的名称代替接收者的具体类型名作为服务名。

Server.ServeCodec

func (server *Server) ServeCodec(codec ServerCodec)

ServeCodec 类似于 ServeConn,但使用指定的编解码器来解码请求和编码响应。

Server.ServeConn

func (server *Server) ServeConn(conn io.ReadWriteCloser)

ServeConn 在单个连接上运行服务器。ServeConn 会阻塞,直到客户端挂断。调用者通常在 go 语句中调用 ServeConn。ServeConn 在连接上使用 gob 线格式。要使用替代编解码器,请使用 ServeCodec。

Server.ServeHTTP

func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP 实现一个 http.Handler,用于响应 RPC 请求。

示例:

server := rpc.NewServer()
server.Register(new(Arith))
http.Handle("/rpc", server)

Server.ServeRequest

func (server *Server) ServeRequest(codec ServerCodec) error

ServeRequest 类似于 ServeCodec,但同步服务单个请求。它不会在完成后关闭编解码器。

ServerCodec

ServerCodec 实现了 RPC 会话服务器侧的 RPC 请求读取和 RPC 响应写入。

type ServerCodec interface {
    ReadRequestHeader(*Request) error
    ReadRequestBody(any) error
    WriteResponse(*Response, any) error
    Close() error
}

方法说明:

  • ReadRequestHeader - 读取请求头
  • ReadRequestBody - 读取请求体
  • WriteResponse - 写入响应
  • Close - 关闭编解码器

ServerError

ServerError 表示从 RPC 连接的远程端返回的错误。

type ServerError string

ServerError.Error

func (e ServerError) Error() string

Error 返回错误的字符串表示。


五、Dial 相关函数

Dial

func Dial(network, address string) (*Client, error)

Dial 连接到指定网络地址的 RPC 服务器。

参数:

  • network - 网络类型(“tcp”, “unix” 等)
  • address - 服务器地址

返回值:

  • *Client - RPC 客户端
  • error - 连接错误

示例:

client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

DialHTTP

func DialHTTP(network, address string) (*Client, error)

DialHTTP 连接到在默认 HTTP RPC 路径上监听的 HTTP RPC 服务器。

参数:

  • network - 网络类型
  • address - 服务器地址

示例:

client, err := rpc.DialHTTP("tcp", "localhost:1234")
if err != nil {
    log.Fatal(err)
}

DialHTTPPath

func DialHTTPPath(network, address, path string) (*Client, error)

DialHTTPPath 连接到在指定网络地址和路径上的 HTTP RPC 服务器。

参数:

  • network - 网络类型
  • address - 服务器地址
  • path - RPC 路径

示例:

client, err := rpc.DialHTTPPath("tcp", "localhost:1234", "/myrpc")
if err != nil {
    log.Fatal(err)
}

NewClient

func NewClient(conn io.ReadWriteCloser) *Client

NewClient 返回一个新的 Client,用于处理连接另一端的服务集请求。它在连接的写入侧添加了一个缓冲区,以便头部和负载作为一个单元发送。

示例:

conn, err := net.Dial("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
client := rpc.NewClient(conn)
defer client.Close()

NewClientWithCodec

func NewClientWithCodec(codec ClientCodec) *Client

NewClientWithCodec 类似于 NewClient,但使用指定的编解码器来编码请求和解码响应。

示例:

conn, err := net.Dial("tcp", ":1234")
if err != nil {
    log.Fatal(err)
}
codec := jsonrpc.NewClientCodec(conn)
client := rpc.NewClientWithCodec(codec)

六、NewServer 相关函数

NewServer

func NewServer() *Server

NewServer 返回一个新的 Server。

示例:

server := rpc.NewServer()
err := server.Register(new(Arith))
if err != nil {
    log.Fatal(err)
}

七、典型示例

示例 1:TCP RPC 服务器和客户端

// 服务器
package main

import (
    "net"
    "net/rpc"
    "log"
)

type Args struct{ A, B int }
type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith)
    
    lis, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal(err)
    }
    defer lis.Close()
    
    log.Println("TCP RPC server listening on :1234")
    for {
        conn, err := lis.Accept()
        if err != nil {
            log.Println("accept error:", err)
            continue
        }
        go rpc.ServeConn(conn)
    }
}

// 客户端
package main

import (
    "fmt"
    "net/rpc"
    "log"
)

type Args struct{ A, B int }

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    var reply int
    err = client.Call("Arith.Multiply", &Args{10, 20}, &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("10 * 20 = %d\n", reply)
}

运行结果:

10 * 20 = 200

示例 2:HTTP RPC 服务器

package main

import (
    "fmt"
    "net"
    "net/http"
    "net/rpc"
    "log"
)

type Args struct{ A, B int }
type Arith int

func (t *Arith) Add(args *Args, reply *int) error {
    *reply = args.A + args.B
    return nil
}

func (t *Arith) Subtract(args *Args, reply *int) error {
    *reply = args.A - args.B
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()
    
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("HTTP RPC server on :8080")
    http.Serve(lis, nil)
}

示例 3:自定义服务名

package main

import (
    "net/rpc"
    "log"
)

type Calculator struct{}

func (c *Calculator) Add(args *Args, reply *int) error {
    *reply = args.A + args.B
    return nil
}

func main() {
    calc := new(Calculator)
    
    // 使用自定义名称注册
    err := rpc.RegisterName("MyCalculator", calc)
    if err != nil {
        log.Fatal(err)
    }
    
    // 客户端调用:"MyCalculator.Add"
}

示例 4:异步 RPC 调用

package main

import (
    "fmt"
    "net/rpc"
    "log"
    "sync"
)

type Args struct{ A, B int }

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    var wg sync.WaitGroup
    
    // 发起 5 个异步调用
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            
            var reply int
            call := client.Go("Arith.Multiply", &Args{i, i}, &reply, nil)
            <-call.Done
            
            if call.Error != nil {
                log.Println("error:", call.Error)
                return
            }
            fmt.Printf("%d * %d = %d\n", i, i, reply)
        }(i)
    }
    
    wg.Wait()
}

示例 5:使用 JSON-RPC

package main

import (
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "log"
)

type Args struct{ A, B int }
type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith)
    
    lis, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal(err)
    }
    
    for {
        conn, err := lis.Accept()
        if err != nil {
            log.Println("accept error:", err)
            continue
        }
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

示例 6:错误处理

package main

import (
    "errors"
    "net/rpc"
    "fmt"
)

type Args struct{ A, B int }
type Arith int

func (t *Arith) Divide(args *Args, reply *int) error {
    if args.B == 0 {
        return errors.New("divide by zero")
    }
    *reply = args.A / args.B
    return nil
}

func main() {
    client, _ := rpc.Dial("tcp", "localhost:1234")
    defer client.Close()
    
    var reply int
    err := client.Call("Arith.Divide", &Args{10, 0}, &reply)
    if err != nil {
        fmt.Println("Error:", err) // divide by zero
    }
}

示例 7:多个服务注册

package main

import (
    "net/rpc"
    "log"
)

type ServiceA struct{}
type ServiceB struct{}

func (s *ServiceA) MethodA(args *string, reply *string) error {
    *reply = "ServiceA response"
    return nil
}

func (s *ServiceB) MethodB(args *string, reply *string) error {
    *reply = "ServiceB response"
    return nil
}

func main() {
    // 注册多个服务
    rpc.Register(new(ServiceA))
    rpc.Register(new(ServiceB))
    
    // 客户端可以调用:
    // "ServiceA.MethodA"
    // "ServiceB.MethodB"
}

示例 8:带超时的 RPC 调用

package main

import (
    "net/rpc"
    "time"
    "log"
)

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    done := make(chan error, 1)
    var reply string
    
    go func() {
        done <- client.Call("Service.Method", "args", &reply)
    }()
    
    select {
    case err := <-done:
        if err != nil {
            log.Fatal(err)
        }
        log.Println("Response:", reply)
    case <-time.After(5 * time.Second):
        log.Fatal("Timeout waiting for response")
    }
}

八、最佳实践

1. 使用 go 语句处理连接

// ✓ 正确
for {
    conn, err := lis.Accept()
    if err != nil {
        continue
    }
    go rpc.ServeConn(conn)
}

// ✗ 错误 - 会阻塞
for {
    conn, err := lis.Accept()
    rpc.ServeConn(conn) // 阻塞,无法处理下一个连接
}

2. 使用 defer 关闭客户端

client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

3. 异步调用使用缓冲通道

// ✓ 正确 - 使用缓冲通道
done := make(chan *rpc.Call, 1)
client.Go("Service.Method", args, reply, done)

// ✗ 错误 - 未缓冲通道可能导致死锁
done := make(chan *rpc.Call)
client.Go("Service.Method", args, reply, done)

4. 错误类型设计

type Args struct {
    A, B int
}

type Result struct {
    Value int
    Error string
}

func (t *T) Method(args *Args, reply *Result) error {
    if args.B == 0 {
        reply.Error = "divide by zero"
        return nil // 或者返回 error
    }
    reply.Value = args.A / args.B
    return nil
}

5. 服务名规范

// 使用清晰的服务名
rpc.RegisterName("UserService", userService)
rpc.RegisterName("OrderService", orderService)

// 客户端调用
client.Call("UserService.GetUser", ...)
client.Call("OrderService.CreateOrder", ...)

九、与其他包配合

1. 与 net/http 配合

import (
    "net/http"
    "net/rpc"
)

rpc.Register(service)
rpc.HandleHTTP()
http.ListenAndServe(":8080", nil)

2. 与 net/rpc/jsonrpc 配合

import (
    "net/rpc"
    "net/rpc/jsonrpc"
)

// 服务器
codec := jsonrpc.NewServerCodec(conn)
rpc.ServeCodec(codec)

// 客户端
codec := jsonrpc.NewClientCodec(conn)
client := rpc.NewClientWithCodec(codec)

3. 与 encoding/gob 配合

import (
    "encoding/gob"
    "net/rpc"
)

// 注册自定义类型
gob.Register(&CustomType{})

// RPC 会自动使用 gob 编码

4. 与 context 配合(超时控制)

import (
    "context"
    "net/rpc"
    "time"
)

func callWithTimeout(client *rpc.Client, method string, args, reply interface{}, timeout time.Duration) error {
    done := make(chan error, 1)
    go func() {
        done <- client.Call(method, args, reply)
    }()
    
    select {
    case err := <-done:
        return err
    case <-time.After(timeout):
        return context.DeadlineExceeded
    }
}

十、快速参考

函数总览

函数说明
Accept接受连接并服务到 DefaultServer
HandleHTTP注册 HTTP 处理器到 DefaultServer
Register在 DefaultServer 注册方法
RegisterName使用自定义名注册方法
ServeCodec使用指定编解码器服务
ServeConn在单个连接上服务
ServeRequest同步服务单个请求

类型总览

类型说明
Call活动的 RPC 调用
ClientRPC 客户端
ClientCodec客户端编解码器接口
RequestRPC 请求头
ResponseRPC 响应头
ServerRPC 服务器
ServerCodec服务器编解码器接口
ServerErrorRPC 错误类型

Client 方法

方法说明
Call同步调用
Close关闭连接
Go异步调用

Server 方法

方法说明
Accept接受连接
HandleHTTP注册 HTTP 处理器
Register注册方法
RegisterName自定义名注册
ServeCodec使用编解码器服务
ServeConn单连接服务
ServeHTTPHTTP 处理器实现
ServeRequest同步服务单请求

Dial 函数

函数说明
Dial连接到 TCP RPC 服务器
DialHTTP连接到 HTTP RPC 服务器(默认路径)
DialHTTPPath连接到 HTTP RPC 服务器(自定义路径)
NewClient从连接创建客户端
NewClientWithCodec使用编解码器创建客户端

十一、注意事项

1. 包已冻结

// net/rpc 包已冻结,不再接受新特性
// 对于新项目,考虑使用 gRPC 或其他现代 RPC 框架

2. 方法签名要求

// ✓ 正确
func (t *T) Method(arg *Args, reply *Result) error

// ✗ 错误 - 第二个参数不是指针
func (t *T) Method(arg *Args, reply Result) error

// ✗ 错误 - 返回值不是 error
func (t *T) Method(arg *Args, reply *Result) int

// ✗ 错误 - 参数超过 2 个
func (t *T) Method(arg1 *Args, arg2 *Args2, reply *Result) error

3. 类型必须导出

// ✓ 正确 - 首字母大写
type Args struct {
    A, B int
}

// ✗ 错误 - 首字母小写,无法被 gob 编码
type args struct {
    A, B int
}

4. 并发安全

// Client 是并发安全的
var client *rpc.Client
go client.Call(...) // ✓ 安全
go client.Call(...) // ✓ 安全

5. 错误处理

// 服务器返回的错误在客户端是 ServerError 类型
err := client.Call("Service.Method", args, reply)
if err != nil {
    if se, ok := err.(rpc.ServerError); ok {
        // 处理服务器返回的错误
    }
}

6. 连接管理

// 总是关闭客户端
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// 或者使用 Close() 的返回值
if err := client.Close(); err != nil {
    log.Println("close error:", err)
}

7. 异步调用完成通道

// done 通道必须是缓冲的,或者使用 nil
call := client.Go("Service.Method", args, reply, make(chan *rpc.Call, 1))
// 或者
call := client.Go("Service.Method", args, reply, nil) // 自动创建缓冲通道

8. 服务名冲突

// 错误 - 同一类型不能注册多次
rpc.Register(new(Arith))
rpc.Register(new(Arith)) // 错误

// 正确 - 使用不同名称
rpc.RegisterName("ArithV1", new(Arith))
rpc.RegisterName("ArithV2", new(Arith))

最后更新: 2026-04-05
Go 版本: Go 1.0+(包已冻结)
包文档: https://pkg.go.dev/net/rpc
相关包: net/rpc/jsonrpc, encoding/gob, net/http
替代方案: gRPC (google.golang.org/grpc)