
为什么 net/rpc 默认只支持 Go 语言客户端?
因为 net/rpc 的默认编解码器是 gob,它专为 Go 类型设计,序列化结果不跨语言。其他语言(如 Python、JS)无法原生解析 gob 流,直接连上去会卡在读取头或报 invalid gob magic number 错误。
常见错误现象:rpc: server cannot decode request: gob: unknown type id or corrupted data,或客户端收不到响应、连接被静默关闭。
- 如果只需要 Go 服务 ↔ Go 客户端,用默认
gob最简单,无需额外配置 - 想对接非 Go 系统,必须显式替换编解码器,比如用
jsonrpc(需改用net/rpc/jsonrpc包) -
jsonrpc模块不自动注册服务,仍要调用rpc.Register,且结构体字段必须导出(首字母大写) - HTTP 传输时,
jsonrpc要求 POST 请求,Content-Type 必须是application/json,否则服务端直接返回 405
如何让 net/rpc 服务跑在 HTTP 上?
标准库支持把 RPC 服务挂到 HTTP handler 上,但不是为了做 REST API,而是复用 HTTP 连接管理(如 Keep-Alive)和反向代理兼容性。关键点:它仍是 RPC 协议语义,只是走 HTTP 底层。
使用场景:已有 Nginx 或 Envoy 做 TLS 终止或限流,想透传 RPC 流量;或避免额外开 TCP 端口。
立即学习“go语言免费学习笔记(深入)”;
- 服务端用
http.Serve+rpc.HandleHTTP(),后者会注册两个固定路径:/_goRPC_/debug(调试页)和/_goRPC_/(主服务) - 客户端不能用普通 HTTP client 发 GET,必须用
rpc.DialHTTP或rpc.DialHTTPPath,它会自动发 POST 到/_goRPC_/ - 路径不可自定义——
DialHTTPPath第二个参数是服务路径,但必须和服务器注册的一致,硬编码为"/_goRPC_/" - 调试页
/_goRPC_/debug返回 HTML,仅用于人工查看注册方法,生产环境建议关掉(删掉rpc.HandleHTTP()后的http.Handle注册)
rpc.Register 和 rpc.RegisterName 有什么实际区别?
区别在于服务名是否可控。默认 Register 用类型名(如 "Arith"),而 RegisterName 允许你指定任意字符串作为服务标识符,这对版本演进和灰度发布很关键。
容易踩的坑:客户端调用时写的 service name 必须和服务端注册时完全一致,包括大小写和下划线;不一致会导致 rpc: can't find service Arith.Multiply。
-
rpc.Register(&Arith{})→ 客户端必须用"Arith.Multiply" -
rpc.RegisterName("v2.Arith", &Arith{})→ 客户端必须用"v2.Arith.Multiply" - 结构体方法必须是导出的(首字母大写)、参数和返回值都得是 public 类型,且第二个参数必须是指针(用于写入响应)
- 不要注册匿名 struct 实例,反射获取类型名不稳定,可能导致服务名为空或乱码
为什么客户端 Call 后程序没退出,还卡着?
因为 rpc.Client 默认不主动关连接,底层 net.Conn 保持打开状态,GC 不会自动回收。如果程序逻辑里忘了 client.Close(),进程就一直持有 socket,表现为“卡住”或资源泄漏。
性能影响:每个未关闭的 client 占一个文件描述符,高并发下很快 hit ulimit;更隐蔽的是,TCP 连接处于 TIME_WAIT 状态,可能阻塞新连接建立。
- 务必在业务逻辑结束前调用
client.Close(),推荐 defer:defer client.Close() - 如果用
rpc.DialHTTP,同样要 close;它返回的也是*rpc.Client - 注意:
Call和Go方法本身不负责连接生命周期,它们只发请求、等响应 - 测试时容易忽略这点——main 函数里
Call完直接 return,没 close,进程就 hang 在那里等 GC,实际不会退出
最常被忽略的其实是连接复用粒度:一个 *rpc.Client 实例可安全并发调用多个方法,没必要每次请求都 Dial 一次;但反过来,跨 goroutine 共享 client 时,也别在某个 goroutine 里提前 close 掉它。










