必须用 gRPC ServerStreaming,HTTP 层不支持原生服务端流;proto 中需定义 stream HelloReply,漏写会导致编译错误;package 名须匹配目录结构;srv.Send() 必须在同 goroutine 顺序调用,否则 panic。

必须用 gRPC ServerStreaming,HTTP 层不支持原生服务端流。Kratos 的 HTTP 服务端不提供类似 SSE 或分块传输的流式响应能力,所有“持续推送”类需求(如日志 tail、实时事件、大文件分块)只能走 gRPC 的 stream 返回类型,否则会卡在协议层或触发连接重置。
proto 文件里 stream 关键字不能漏
定义方法时,返回类型必须显式写成 stream HelloReply,不是 HelloReply:
service Greeter {
rpc SayHelloStream (HelloRequest) returns (stream HelloReply);
}
常见错误现象:
- 漏写
stream→ 生成代码里没有SayHelloStream方法签名,编译报错undefined: v1.Greeter_SayHelloStreamServer - 把请求也写成
stream HelloRequest→ 变成客户端流,服务端收不到首条消息就阻塞等待 -
package名与api/目录深度不一致(如api/greeter/v1/greeter.proto却写package greeter;)→ 生成的v1.Greeter_SayHelloStreamServer类型路径错乱,注册时报cannot use *GreeterService as type v1.GreeterServer
srv.Send() 必须在同一个 goroutine 里顺序调用
生成的服务方法签名形如 func (*GreeterService) SayHelloStream(ctx context.Context, req *v1.HelloRequest, srv v1.Greeter_SayHelloStreamServer) error,其中 srv 是流式服务端对象,它的 Send() 方法不是并发安全的:
- 起新 goroutine 调用
srv.Send()→ 运行时 panic:send on closed channel - 用
time.AfterFunc或select+case 触发发送 → 本质仍是异步,同样出错 - 正确做法:所有
srv.Send()放在当前函数的 for 循环或 if 分支中,靠time.Sleep控制节奏,不脱离主协程
示例片段:
func (s *GreeterService) SayHelloStream(ctx context.Context, req *v1.HelloRequest, srv v1.Greeter_SayHelloStreamServer) error {
for i := 0; i <h3>服务注册和启动配置要匹配</h3><p>生成的 service 文件只是骨架,不注册就不会生效:</p>
- 忘记在
internal/server/grpc.go中调用v1.RegisterGreeterServer(grpcServer, &service.GreeterService{})→ 启动后 gRPC Server 根本不识别该方法,客户端调用直接报UNIMPLEMENTED - 删掉自动生成的断言行
var _ v1.GreeterServer = (*GreeterService)(nil)→ 编译仍过,但运行时若漏实现某个方法(包括流式方法),会 panic:missing implementation for SayHelloStream - gRPC Server 启动时没配
grpc.MaxConcurrentStreams→ 高频流式请求下容易触发RESOURCE_EXHAUSTED错误,建议设为1000或更高
服务端监听配置示例:
srv := grpc.NewServer(
grpc.Address(":9000"),
grpc.MaxConcurrentStreams(1000),
)
最容易被忽略的是:流式响应的生命周期完全绑定在单次 RPC 调用上,客户端一旦断开,srv.Send() 立即失败;而服务端无法主动感知断连,只能靠下一次 Send() 返回 error 来退出循环——所以每个 Send() 后都得检查 error,不能只在循环末尾判一次。











