Go net/rpc错误需同时检查两层:client.Call返回的err表示网络/协议失败,call.Error非nil才表示服务端业务错误;方法签名必须为func(T,S)error且用指针,panic会导致连接中断,建议用jsonrpc提升错误可读性。

Go net/rpc 的错误返回机制不走 error 返回值
Go 标准库的 net/rpc(尤其是基于 HTTP 或 TCP 的服务端)不会把方法执行中的 panic 或 error 作为函数返回值抛给客户端。它只通过响应体里的 ErrorResponse 字段传递错误,且**仅当方法签名形如 func(*Args, *Reply) error 且返回非 nil error 时**,才会被序列化为 RPC 错误。否则,panic 会导致连接中断,客户端收到 rpc: server failed to decode args: EOF 这类底层错误,无法区分业务逻辑失败和网络异常。
- 必须确保 RPC 方法签名严格符合
func(*T, *S) error形式,*T和*S都要是指针类型 - 不要在方法内部直接
log.Fatal或os.Exit,这会终止整个 server - 如果想透传底层 error(比如数据库超时),建议包装成自定义 error 并实现
Error()方法,便于客户端解析
客户端如何可靠判断 RPC 调用是否失败
调用 client.Call 或 client.Go 后,不能只检查返回的 err 是否为 nil —— 这个 err 仅代表“调用发起失败”(如连接 refused、序列化失败),不代表业务逻辑执行失败。真正的业务错误藏在 reply 对应结构体的某个字段里,或者更常见的是:只有当 call.Error != nil 时,才表示服务端明确返回了 error。
-
call.Error是服务端方法返回的error值反序列化结果,可能为"invalid user id"这类字符串,也可能是 JSON 编码后的结构体(取决于编码器) - 标准
gob编码下,call.Error类型是*rpc.Error,其Err字段是 string;若用jsonrpc,则可能是 map 或自定义结构 - 务必同时检查:
if err != nil { /* 网络/协议层失败 */ }和if call.Error != nil { /* 业务逻辑失败 */ }
用 jsonrpc 替代默认 gob 提升错误可读性
默认 gob 编码对 error 的序列化是二进制且不可读的,调试困难;jsonrpc 把 error 映射为 JSON-RPC 2.0 规范的 error 对象(含 code、message、data),更适合跨语言或前端联调场景。
package mainimport ( "net/rpc/jsonrpc" "net/http" )
func main() { http.Handle("/rpc", jsonrpc.NewHTTPServer(&MyService{})) http.ListenAndServe(":8080", nil) }
type MyService struct{}
立即学习“go语言免费学习笔记(深入)”;
func (s MyService) Divide(args DivArgs, reply *DivReply) error { if args.B == 0 { return &jsonrpc.RPCError{ Code: -32602, Message: "division by zero", Data: map[string]string{"field": "b"}, } } reply.Result = args.A / args.B return nil }
-
jsonrpc.RPCError是标准兼容结构,客户端(如 curl 或 JS fetch)能直接识别code和message - 注意:服务端需用
jsonrpc.NewHTTPServer,客户端需用jsonrpc.DialHTTP或手动构造 JSON-RPC 请求体 - 避免在
Data中塞敏感信息或大对象,JSON 序列化有开销
服务端 panic 导致连接重置的兜底处理
即使写了 defer/recover,RPC handler 内部未捕获的 panic 仍会让 net/rpc 关闭连接,客户端看到类似 read tcp ...: i/o timeout 或 broken pipe。这不是 bug,是设计使然 —— RPC 框架不负责 recover。
- 最稳妥的方式是在每个注册的方法里加统一 recover:
func (s *MyService) SafeMethod(args *Args, reply *Reply) error {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in SafeMethod: %v", r)
// 返回一个明确的业务错误,而非让连接断开
}
}()
// 实际逻辑...
return nil
}- 也可以用中间件思路:封装一个
wrapHandler函数,自动注入 defer-recover,再注册到 server - 但要注意:recover 后不能继续写 response,因为连接可能已半关闭;只能返回 error,由 RPC 框架统一处理
实际项目中,最容易被忽略的是「同时检查两层 error」—— 网络层 err 和业务层 call.Error。漏掉任一,都会让错误流丢失上下文,排查时只能靠日志猜。










