在 gRPC 中,流式通信(Streaming)是实现高性能、实时交互的核心功能之一。本文将通过实际代码示例,结合详细注释和原理说明,帮助你彻底掌握 gRPC 的四种流式通信模式(Unary、Server Streaming、Client Streaming、Bidirectional Streaming),并理解它们的适用场景。
gRPC 基于 HTTP/2 协议,支持 全双工通信,允许客户端和服务端之间建立持续的数据流。相比传统的单次请求-响应模式,流式通信更适合以下场景:
模式名称 | 客户端发送次数 | 服务端返回次数 | 典型场景 |
---|---|---|---|
Unary(简单模式) | 1次 | 1次 | 用户信息查询 |
Server Streaming | 1次 | 多次 | 股票实时行情推送 |
Client Streaming | 多次 | 1次 | 物联网设备批量上报数据 |
Bidirectional Streaming | 多次 | 多次 | 实时聊天、语音识别 |
我将在下面着重讲解后三种,第一种和大家平时熟悉的rpc没有太大区别,所以不在详细介绍。
.proto
接口定义syntax = "proto3";
option go_package=".;proto1";
service Greeter {
// 服务端流模式:客户端发送一次请求,服务端持续返回数据
rpc GetStream(StreamReqData) returns (stream StreamResData);
// 客户端流模式:客户端持续发送数据,服务端最终返回结果
rpc PutStream(stream StreamReqData) returns (StreamResData);
// 双向流模式:双方均可随时发送数据
rpc AllStream(stream StreamReqData) returns (stream StreamResData);
}
message StreamReqData {
string data = 1; // 请求数据
}
message StreamResData {
string data = 1; // 响应数据
}
关键点解析:
.proto
文件定义服务和消息结构,这是 gRPC 的核心设计起点。stream
关键字标明通信方向(客户端/服务端流式)。go_package
指定生成代码的 Go 包路径,确保代码组织清晰。type server struct {
proto1.UnimplementedGreeterServer // 实现未实现的接口方法
}
UnimplementedGreeterServer
,确保服务端必须实现所有定义的方法。func (s *server) GetStream(req *StreamReqData, res Greeter_GetStreamServer) error {
i := 0
for {
i++
_ = res.Send(&StreamResData{
Data: fmt.Sprintf("%v", time.Now().Unix()),
})
time.Sleep(time.Second)
if i > 10 {
break
}
}
return nil
}
Greeter_GetStreamServer
是自动生成的接口,提供 Send
方法发送数据。for
循环持续调用 Send
,实现服务端流式响应。i > 10
控制发送次数,模拟实时数据推送场景(如股票行情)。func (s *server) PutStream(cliStr Greeter_PutStreamServer) error {
for {
if a, err := cliStr.Recv(); err != nil {
fmt.Println(err)
break
} else {
fmt.Println(a.Data)
}
}
return nil
}
Greeter_PutStreamServer
提供 Recv
方法接收客户端数据。for
循环持续接收客户端发送的数据(如物联网设备上报)。Recv
返回错误并终止循环。func (s *server) AllStream(allStr Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
// 接收客户端数据
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到客户端消息:" + data.Data)
}
}()
// 发送数据给客户端
go func() {
defer wg.Done()
for {
_ = allStr.Send(&StreamResData{Data: "我是服务器"})
time.Sleep(time.Second)
}
}()
wg.Wait()
return nil
}
sync.WaitGroup
管理两个 goroutine 的生命周期。WaitGroup
确保主协程等待子协程完成后再退出。conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
设计解析:
grpc.Dial
创建到服务端的连接,WithInsecure
表示不使用 TLS。defer conn.Close()
确保程序退出时关闭连接。res, _ := c.GetStream(context.Background(), &StreamReqData{Data: "慕课网"})
for {
a, err := res.Recv()
if err != nil {
fmt.Println(err)
break
}
fmt.Println(a.Data)
}
res
是自动生成的 ServerStream
接口,提供 Recv
方法接收数据。Recv
直到服务端关闭流(如股票行情推送)。context.Background()
控制调用的生命周期。putS, _ := c.PutStream(context.Background())
i := 0
for {
i++
_ = putS.Send(&StreamReqData{
Data: fmt.Sprintf("慕课网%d", i),
})
time.Sleep(time.Second)
if i > 10 {
break
}
}
putS
是自动生成的 ClientStream
接口,提供 Send
方法发送数据。i > 10
触发循环结束,服务端会收到 EOF 错误并终止。allStr, _ := c.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
// 接收服务端消息
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到服务端消息:" + data.Data)
}
}()
// 发送消息给服务端
go func() {
defer wg.Done()
for {
_ = allStr.Send(&StreamReqData{Data: "慕课网"})
time.Sleep(time.Second)
}
}()
wg.Wait()
allStr
进行双向数据交换。sync.WaitGroup
确保主协程等待子协程完成。ServerStream
、ClientStream
等接口sync.WaitGroup
管理多协程生命周期context.Context
控制流式通信的取消和超时grpclog
库输出调试日志grpcurl
工具进行接口测试.proto
文件定义清晰的接口和数据结构。ServerStream
)已封装底层通信细节。sync.WaitGroup
实现并发控制。Recv
和 Send
的返回错误。通过本文的实践和解析,我们深入理解了 gRPC 的四种流式通信模式,并掌握了对应的实现方式。以下是关键收获:
模式类型 | 核心价值 | 注意事项 |
---|---|---|
Server Streaming | 实时数据推送 | 需处理客户端断开连接的情况 |
Client Streaming | 批量数据上传 | 需处理服务端处理超时问题 |
Bidirectional | 实时双向交互 | 需特别注意协程死锁和资源释放 |
Unary | 传统 RPC 模式 | 最简单的实现方式 |
总之:
gRPC 的流式通信就像一条双向车道,可以根据交通流量(数据传输需求)灵活调整通行方向,为现代分布式系统提供高效、灵活的通信方案。