go标准库net/rpc只支持tcp和http,因其不抽象传输层,仅依赖net.conn或http.responsewriter实现通信,底层由go原生网络库提供支持,无法直接适配udp或websocket等非流式协议。

Go标准库net/rpc为什么只支持TCP和HTTP?
因为net/rpc本身不抽象传输层,它只负责序列化、方法路由和错误包装。底层通信完全交给net.Conn或http.ResponseWriter——前者要求实现Read/Write,后者靠rpc.HandleHTTP()把RPC请求映射到/rpc路径并复用HTTP的连接管理。所以你没法直接用UDP或WebSocket,除非自己包装net.Conn实现可靠字节流。
常见误区是以为rpc.DialHTTP("tcp", ...)里的"tcp"指协议类型,其实它只是rpc.Dial的网络类型参数,和HTTP共存时会被忽略;真正走HTTP时用的是rpc.DialHTTP("tcp", addr)或rpc.DialHTTP("unix", ...),但底层仍是HTTP over TCP。
-
rpc.ServeConn(conn)只吃net.Conn,不管它是TCP、Unix socket还是mock的内存管道 -
rpc.HandleHTTP()注册的是http.DefaultServeMux下的"/rpc"handler,必须配合http.Serve或http.ListenAndServe - 想换协议?得自己实现
ClientCodec和ServerCodec,并传给rpc.NewClientWithCodec或rpc.ServeRequest
rpc.Register和rpc.RegisterName的区别在哪?
rpc.Register用结构体类型名作服务名(如Arith),rpc.RegisterName("FoodService", &FoodService{})则显式指定服务名。关键差异在方法反射:注册时会遍历结构体所有公开方法,检查签名是否符合func(*T, *Args, *Reply) error,且*T必须是指针接收者。
容易踩的坑:
- 方法接收者不是指针(如
func(t Arith) Multiply(...))→ 注册成功但调用时返回"method not found" - 参数不是两个指针(如
func(*T, Args, *Reply) error)→rpc包在反射阶段就跳过该方法,不报错也不注册 - 服务名重复注册 → 后一次覆盖前一次,无警告
- 结构体字段未导出(小写开头)→
gob编码失败,客户端收到"EOF"或空响应
为什么客户端Call后经常卡住或返回"invalid character"?
绝大多数情况是编解码不匹配:net/rpc默认用gob,但gob要求客户端和服务端**完全一致的结构体定义**(包括包路径、字段顺序、是否导出)。一旦服务端改了Args字段名,或客户端用了不同包的同名结构体,gob.Decoder就会解析失败,表现为Call阻塞(因连接未关闭)或返回乱码错误。
调试建议:
- 先确认两端
Args/Reply结构体定义100%相同,连注释都不能差 - 用
rpc.ServeRequest+ 自定义ServerCodec捕获原始字节流,看是否真能收发 - 避免混用
rpc.DialHTTP和rpc.Dial:前者走HTTP POST body,后者走裸TCP流,编码器虽都是gob,但HTTP版会在body外再套一层HTTP头,错配直接404或500 - 超时必须手动控制:原生
rpc.Client不支持调用级超时,得用context.WithTimeout包装conn,或改用rpc.NewClientWithCodec自定义codec
生产环境为什么几乎不用net/rpc?
不是它不能用,而是它的设计假设太理想:gob绑定Go生态、无服务发现、无重试、无负载均衡、无中间件扩展点。当你要对接Python服务、需要灰度发布、或调用链要埋点时,net/rpc的硬编码逻辑(比如强制两参数+error签名、固定gob)就成了枷锁。
真正被忽略的复杂点是连接生命周期管理:rpc.Client没有连接池,每次Dial都新建TCP连接;rpc.ServeConn是单连接单goroutine模型,无法应对高并发短连接——这些在gRPC里由grpc.ClientConn和Server内置解决,但在net/rpc里得全自己撸。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











