无缓冲channel能严格保证rpc中「发一次、收一次」的配对阻塞同步,避免错序或丢失;若用带缓冲channel易致超时误判或响应错乱;select中超时必须与channel操作同级,time.after需置于case内。

为什么用无缓冲 channel 封装 RPC 同步响应
因为 RPCClient 需要严格保证「发一次、收一次」的配对关系,无缓冲 channel 能天然阻塞发送方直到接收方就绪,避免消息丢失或错序。一旦换成带缓冲 channel(比如 make(chan string, 1)),就可能出现客户端发完请求立刻返回、还没等服务端写入就超时,或者多个请求挤在缓冲里导致响应错乱。
RPCClient 函数里 select 超时判断的坑
常见错误是把 time.After 放在 select 外部,导致定时器提前启动,和 channel 操作不同步。正确做法必须让 time.After(time.Second) 和 同时参与 <code>select 分支,否则超时时间可能从函数进入就开始计,而不是从发送请求那一刻起算。
- 错误写法:
timeout := time.After(time.Second); select { case ack := —— timeout 已启动,哪怕 <code>ch 还没执行完 - 正确写法:超时分支直接写
,确保和 channel 操作原子绑定 - 如果需要更精确控制(比如重试),建议把超时时间作为参数传入,而不是硬编码
time.Second
服务端 RPCServer 必须循环读取,不能只收一次
当前示例里 for { data := 是必须的,否则第二个客户端请求会因 channel 无人接收而永久阻塞。但要注意:这个模型下所有客户端共用一个 channel,没有请求隔离,<code>reply_to 或 correlation_id 类机制完全缺失。
- 单 channel + 单 goroutine 的服务端只适合教学或单连接场景
- 真实 RPC 中,每个请求应有独立响应路径,否则并发调用会互相覆盖返回值
- 若坚持用 channel 模拟,至少得为每次调用创建专属 channel 对(如
reqCh, respCh := make(chan string), make(chan string)),再通过 map 管理生命周期
gob 编码与跨语言兼容性这个雷别踩
net/rpc 默认用 gob 编码,它只能被 Go 程序正确解码。如果你将来想用 Python 或 Java 客户端连这个 RPC 服务,现在写的 RPCClient/RPCServer 会直接失败——不是逻辑错,而是序列化层就不通。
- 跨语言需求明确时,改用
net/rpc/jsonrpc,把gob换成 JSON 编码 - 但注意:JSON 不支持 Go 特有类型(如
time.Time、chan、闭包),结构体字段必须是基础类型或可 JSON 序列化的嵌套结构 - 更彻底的方案是跳过
net/rpc,直接上 gRPC + Protocol Buffers,它原生支持双向流和多语言,且stream定义比手动 channel 封装更健壮
真正难的不是写通一个请求响应,而是让多个并发请求不串、不丢、不错配,channel 封装只是起点,边界条件和扩展路径得提前想清楚。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











