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 调用 |
| Client | RPC 客户端 |
| ClientCodec | 客户端编解码器接口 |
| Request | RPC 请求头 |
| Response | RPC 响应头 |
| Server | RPC 服务器 |
| ServerCodec | 服务器编解码器接口 |
| ServerError | RPC 错误类型 |
Client 方法
| 方法 | 说明 |
|---|---|
| Call | 同步调用 |
| Close | 关闭连接 |
| Go | 异步调用 |
Server 方法
| 方法 | 说明 |
|---|---|
| Accept | 接受连接 |
| HandleHTTP | 注册 HTTP 处理器 |
| Register | 注册方法 |
| RegisterName | 自定义名注册 |
| ServeCodec | 使用编解码器服务 |
| ServeConn | 单连接服务 |
| ServeHTTP | HTTP 处理器实现 |
| 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)