srteam 顾名思义 就是 一种 流,可以源源不断的 推送 数据,很适合 传输一些大数据,或者 服务端 和 客户端 长时间 数据交互,比如 客户端 可以向 服务端 订阅 一个数据,服务端 就 可以利用 stream ,源源不断地 推送数据。
一、介绍
1.服务端数据流
这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
2.客户端数据流
与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
3.双向数据流
顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。
二、代码示例
1.proto文件
syntax = "proto3";
option go_package = ".;proto";
service Greeter{
rpc GetStream (ReqData) returns(stream ResData);//服务端推送流
rpc PutStream(stream ReqData) returns (ResData);//客户端推送流
rpc AllStream(stream ReqData) returns (stream ResData);//双向推送流
}
message ReqData{
string data = 1;
}
message ResData{
string data = 1;
}
2.服务端
package main
import (
"fmt"
"google.golang.org/grpc"
"grpc_demo01/stream/proto"
"log"
"net"
"sync"
"time"
)
type server struct {
}
func (*server) GetStream(reqData *proto.ReqData, res proto.Greeter_GetStreamServer) error {
for i := 0; i < 10; i++ {
err := res.Send(&proto.ResData{Data: fmt.Sprintf("%v", time.Now().Unix())})
if err != nil {
return err
}
time.Sleep(1 * time.Second)
}
return nil
}
func (*server) PutStream(res proto.Greeter_PutStreamServer) error {
for {
recv, err := res.Recv()
if err != nil {
log.Println(err)
break
}
log.Println(recv)
}
return nil
}
func (*server) AllStream(allStr proto.Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
recv, err := allStr.Recv()
if err != nil {
log.Fatal(err)
}
log.Println(recv)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
err := allStr.Send(&proto.ResData{Data: fmt.Sprintf("服务端发送", i, "条信息")})
time.Sleep(1 * time.Second)
if err != nil {
log.Fatal(err)
return
}
}
}()
wg.Wait()
return nil
}
func main() {
//监听端口
lis, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
return
}
//创建一个grpc 服务器
s := grpc.NewServer()
//注册事件
proto.RegisterGreeterServer(s, &server{})
//处理链接
err = s.Serve(lis)
if err != nil {
panic(err)
}
}
3.客户端
package main
import (
"context"
"google.golang.org/grpc"
"grpc_demo01/stream/proto"
"log"
"time"
)
const (
ADDRESS = "localhost:8080"
)
func main() {
//通过grpc 库 建立一个连接
conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
if err != nil {
return
}
defer conn.Close()
//通过刚刚的连接 生成一个client对象。
c := proto.NewGreeterClient(conn)
//调用服务端推送流
reqstreamData := &proto.ReqData{Data: "aaa"}
res, _ := c.GetStream(context.Background(), reqstreamData)
for {
aa, err := res.Recv()
if err != nil {
log.Println(err)
break
}
log.Println(aa)
}
//客户端 推送 流
putRes, err := c.PutStream(context.Background())
i := 1
for {
i++
err := putRes.Send(&proto.ReqData{Data: "ss"})
if err != nil {
log.Println(err)
return
}
time.Sleep(time.Second)
if i > 10 {
break
}
}
//服务端 客户端 双向流
allStr, _ := c.AllStream(context.Background())
go func() {
for {
data, _ := allStr.Recv()
log.Println(data)
}
}()
go func() {
for {
err := allStr.Send(&proto.ReqData{Data: "ssss"})
if err != nil {
log.Println(err)
return
}
time.Sleep(time.Second)
}
}()
select {}
}
三、出现问题处理方式
gRPC 是一个高性能、开源的通用 RPC 框架,用于在不同的服务中实现通信。在 gRPC 流中途传输发生错误时,需要采取相应的错误处理策略。以下是处理 gRPC 流中途传输错误的一些建议:
- 错误检查:在发送和接收数据时,始终检查错误。发送和接收数据的函数通常返回一个错误,如果出现问题,该错误将不为nil。例如,在客户端中,当调用
stream.Send()
或stream.Recv()
时,始终检查返回的错误。
resp, err := stream.Recv()
if err != nil {
if err == io.EOF {
// 流结束,处理流结束的情况
} else {
// 处理其他错误
}
}
- 错误处理:当检测到错误时,可以执行以下操作之一或多个:
-
- 记录错误:使用日志库记录错误信息,以便在调试或分析问题时使用。
- 重试:如果错误是暂时的,可以尝试重新连接或重新发送数据。注意要设置重试次数和延迟,以避免无限循环。
- 返回错误:将错误返回给上游调用者,让其决定如何处理。
- 自定义错误处理:根据错误类型和具体场景执行自定义的错误处理操作。
- 使用 gRPC 中间件:可以使用 gRPC 中间件(例如 grpc-middleware)来实现全局的错误处理和重试策略。这可以帮助减少代码重复和提高代码可读性。
- 超时和取消:使用 gRPC 的超时和取消功能,确保请求在预期时间内完成。如果操作未在预期时间内完成,可以取消请求并处理超时错误。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.SomeStreamingRPC(ctx)
- 使用健康检查:可以使用 gRPC 健康检查(例如 grpc-health-probe)来检查服务的可用性。这样可以在传输发生错误之前识别潜在的问题。
通过遵循以上建议,您可以在 gRPC 流中途传输错误时更好地处理错误和异常情况。根据具体的应用场景和需求,可以根据需要调整错误处理策略。