Kratos gRPC双向数据流

1,126 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Golang 使用Kratos微服务框架搭建基于gRPC的双向数据流Demo,简单的双向通信.

创建项目kratos new grpcStream

cd grpcStream
go generate ./...

go版本 1.17

kratosv2版本 v2.3.0

Proto

编辑 .\api\helloworld\v1\greeter.proto

// 流式请求
message StreamRequest{
  string req = 1;
}
// 流式响应
message StreamResponse{
  string res = 1;
}
// 服务方法
service Stream{
  // 双向流式rpc,同时在请求参数前和响应参数前加上stream
  rpc Conversations(stream StreamRequest) returns(stream StreamResponse){};
}

生成proto对应的go文件 kratos proto client .\api\helloworld\v1\greeter.proto

Service

生成server代码 kratos proto server .\api\helloworld\v1\greeter.proto -t .\internal\service

图片.png (这里已经生成了双向数据流的代码了)

service里的service把Stream加上

var ProviderSet = wire.NewSet(NewGreeterService,NewStreamService)

先简单的测试

go generate ./...

运行 运行种类:软件包(package)

软件包路径(Path):grpcStream/cmd/grpcStream

工作目录为项目目录

参数使用相对路径读取配置 -conf ./configs/config.yaml

图片.png

测试代码

package main

import (
   "context"
   "github.com/go-kratos/kratos/v2/errors"
   "github.com/go-kratos/kratos/v2/middleware/recovery"
   transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
   v1 "grpcStream/api/helloworld/v1"
   "log"
)

// 测试客户端连接
func main() {
   callGRPC()
}

func callGRPC() {
   conn, err := transgrpc.DialInsecure(
      context.Background(),
      transgrpc.WithEndpoint("127.0.0.1:9000"),
      transgrpc.WithMiddleware(
         recovery.Recovery(),
      ),
   )
   if err != nil {
      panic(err)
   }
   defer conn.Close()

   client := v1.NewGreeterClient(conn)
   log.Printf("[grpc] SayHello %+v\n", client)
   reply, err := client.SayHello(context.Background(), &v1.HelloRequest{Name: "Nick"})
   if err != nil {
      log.Fatal(err)
   }
   if errors.IsBadRequest(err) {
      log.Printf("[grpc] SayHello error is invalid argument: %v\n", err)
   }
   log.Printf("[grpc] SayHello %+v\n", reply)
}

图片.png

双向数据流

编写 internal/service/stream.go

package service

import (
   "fmt"
   "io"

   pb "grpcStream/api/helloworld/v1"
)

type StreamService struct {
   pb.UnimplementedStreamServer
}

func NewStreamService() *StreamService {
   return &StreamService{}
}

func (s *StreamService) Conversations(conn pb.Stream_ConversationsServer) error {
   for {
      req, err := conn.Recv()
      if err == io.EOF {
         // 流结束
         fmt.Println("客户端发送的数据流结束")
         return nil
      }
      if err != nil {
         // 流出现错误
         fmt.Println("接收数据出错:", err)
         return err
      }
      // TODO 处理消息
      fmt.Println(req.GetReq())
      // 返回消息
      err = conn.Send(&pb.StreamResponse{
         Res: "Receive " + req.GetReq(),
      })
      if err != nil {
         // 返回出现出现错误
         return err
      }
   }
}

编写 internal/server/grpc.go

// 参数增加 stream *service.StreamService,
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, stream *service.StreamService, logger log.Logger) *grpc.Server {
   var opts = []grpc.ServerOption{
      grpc.Middleware(
         recovery.Recovery(),
      ),
   }
   if c.Grpc.Network != "" {
      opts = append(opts, grpc.Network(c.Grpc.Network))
   }
   if c.Grpc.Addr != "" {
      opts = append(opts, grpc.Address(c.Grpc.Addr))
   }
   if c.Grpc.Timeout != nil {
      opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
   }
   srv := grpc.NewServer(opts...)
   v1.RegisterGreeterServer(srv, greeter)
   v1.RegisterStreamServer(srv, stream) //增加
   return srv
}

go generate ./...

wire_gen要如下图所示,自己写的Stream才会生效

图片.png

测试

test/main.go

package main

import (
   "context"
   "fmt"
   "github.com/go-kratos/kratos/v2/errors"
   "github.com/go-kratos/kratos/v2/middleware/recovery"
   transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
   v1 "grpcStream/api/helloworld/v1"
   "io"
   "log"
)

// 测试客户端连接
func main() {
   callStream()
}

func callStream() {
   conn, err := transgrpc.DialInsecure(
      context.Background(),
      transgrpc.WithEndpoint("127.0.0.1:9000"),
      transgrpc.WithMiddleware(
         recovery.Recovery(),
      ),
   )
   if err != nil {
      panic(err)
   }
   defer conn.Close()

   client := v1.NewStreamClient(conn)
   conversations, err := client.Conversations(context.TODO())
   if err != nil {
      log.Fatalf("Failed  : %v", err)
      return
   }
   waitc := make(chan struct{})
   // 接收
   go func() {
      for {
         in, err := conversations.Recv()
         if err == io.EOF {
            // read done.
            close(waitc)
            return
         }
         if err != nil {
            log.Fatalf("Failed to receive a note : %v", err)
         }
         log.Printf("Got message (%s)", in.GetRes())
      }
   }()

   // 客户端模拟发送
   for n := 0; n < 5; n++ {
      if err := conversations.Send(&v1.StreamRequest{Req: fmt.Sprintf("this is %d", n)}); err != nil {
         log.Fatalf("Failed to send a note: %v", err)
      }
   }

   //最后关闭流
   err = conversations.CloseSend()
   if err != nil {
      log.Fatalf("Conversations close stream err: %v", err)
   }
   <-waitc
}

测试结果

图片.png

左边是服务端接收到消息,然后返回,右边是客户端接收到返回消息输出。

通过chan异步发送消息

模拟聊天...... (下一篇)