net/rpc不支持流式通信,因其基于单次封包解包模型且无http/2多路复用能力;grpc依托http/2帧机制与protobuf长度前缀,原生支持客户端流、服务端流和双向流,需通过.proto中stream关键字精确声明方向并生成对应send()/recv()接口。

Go 语言标准库的 net/rpc 不支持流式通信,硬改没用;真要实现客户端持续发、服务端持续推、或双向实时传数据,必须用 gRPC。
为什么 net/rpc 不能做流式,而 gRPC 可以
net/rpc 基于一次封包+一次解包模型,每次调用都新建连接(或复用但无状态),无法在单次调用中分段收发。gRPC 则依托 HTTP/2 的帧机制,天然支持多路复用和流式语义——同一个 TCP 连接上可并行多个独立 stream,每个 stream 又能分多次 send/recv。
- HTTP/2 的
DATA帧允许把一个逻辑响应拆成多个网络包发送,gRPC 就靠这个实现“服务端流” -
net/rpc的gob编码不带消息边界标记,无法判断一段数据是否完整;gRPC 的 Protocol Buffers 消息自带长度前缀,stream 中每条消息可独立 decode - gRPC 客户端和服务端生成的 stub 都暴露
Send()/Recv()方法,底层自动处理 buffer、错误重试、流生命周期,net/rpc得自己手撸 goroutine + channel 管理,极易出错
proto 文件里 stream 关键字写在哪,决定流方向
流式能力完全由 .proto 文件中的 stream 修饰位置决定,不是靠代码逻辑“模拟”。写错位置,生成的 Go 代码根本不会提供流式方法。
- 服务端流:
rpc Subscribe(Request) returns (stream Event);→ 客户端调用一次,服务端可多次Send() - 客户端流:
rpc Upload(stream Chunk) returns (Status);→ 客户端多次Send(),服务端等全部收完再Return - 双向流:
rpc Chat(stream Message) returns (stream Message);→ 双方各自维护独立的Send()/Recv(),互不阻塞
注意:stream 只能修饰请求或响应类型之一,不能同时修饰两者(那叫“双向流”,是单独语法);也不能修饰 message 定义本身——stream 是 RPC 方法签名的一部分。
生成代码后,流式调用的实际写法
生成的 Go 代码里,流式方法返回的是带 Send()/Recv() 的 stream 接口,不是普通 struct。常见错误是当成普通函数调用,或忽略 error 检查。
- 服务端流示例:客户端拿到
eventStream后,必须用for循环反复Recv(),直到返回io.EOF或其他 error - 客户端流示例:服务端需在 handler 函数内显式循环
Recv(),且不能提前 return,否则客户端会收到rpc error: code = Canceled desc = context canceled - 双向流示例:双方都要起 goroutine 分别处理 send 和 recv,否则会因阻塞导致死锁;必须用
context.WithTimeout控制整体生命周期,否则流可能永远 hang 住
典型漏点:Recv() 返回 nil, io.EOF 是正常结束,不是错误;但 Send() 失败通常意味着连接已断,应立即退出循环并关闭 stream。
容易被忽略的连接与超时配置
流式通信一旦建立,连接会长时间保持,但默认的 keepalive 和 timeout 参数对流式很不友好——比如服务端默认 2 小时 idle 断连,客户端却还在等下一条消息。
- 服务端需显式设置
KeepaliveParams:如MaxConnectionIdle: 30 * time.Minute,避免过早断连 - 客户端调用流式方法时,
context要设足够长的 timeout(甚至用context.Background()),但必须配合业务层心跳或手动 cancel - 遇到
code = Unavailable desc = transport is closing,90% 是服务端证书失效、TLS 配置不一致,或客户端未正确处理 stream 关闭信号
真正麻烦的是流中部分消息失败后的恢复:gRPC 不保证 stream 内消息顺序重传,出错就得整个 stream 重建——这意味着你的业务逻辑得能容忍“断连重连后从头开始”或实现应用层断点续传。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











