gRPC--流式传输

1,027 阅读4分钟

gRPC--流式传输

普通RPC(一问一答式)

编写proto

syntax = "proto3"; // 指定proto版本
package hello_grpc;     // 指定默认包名

// 指定golang包名
option go_package = "/hello_grpc";

//定义rpc服务
service HelloService  {
  // 定义函数
  rpc SayH (Req ) returns (Res) {}
}

// HelloRequest 请求内容
message Req {
  string message = 1;
}

// HelloResponse 响应内容
message Res{
  string message = 1;
}

服务端

package main

import (
  "context"
  "fmt"
  "google.golang.org/grpc"
  "net"
  hello_grpc "rpc_demo/pb"
)

type server struct {
  hello_grpc.UnimplementedHelloGrpcServer
}

func (s *server) SayHi(ctx context.Context, req *hello_grpc.Req) (*hello_grpc.Res, error) {
  fmt.Println(req.GetMessage())
  return &hello_grpc.Res{Message: "Hello " + req.GetMessage()}, nil
}

func main() {
  l, err := net.Listen("tcp", ":8888")
  if err != nil {
    panic(err)
  }
  s := grpc.NewServer()
  hello_grpc.RegisterHelloGrpcServer(s, &server{})
  s.Serve(l)
}

客户端

package main

import (
  "context"
  "fmt"
  "google.golang.org/grpc"
  hello_grpc "rpc_demo/pb"
)

type server struct {
}

func main() {
  conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
  defer conn.Close()
  if err != nil {
    panic(err)
  }

  client := hello_grpc.NewHelloGrpcClient(conn)
  req, _ := client.SayHi(context.Background(), &hello_grpc.Req{Message: "我从客户端来"})
  fmt.Println(req.GetMessage())
}

流式传输

流式定义


service ServicesStream{
  rpc Search(Req) returns (Res); //传统的 即刻响应的
  rpc SayHiInStream(stream Req) returns (Res); //入参为流
  rpc SayHiOutStream(Req) returns (stream Res); //入参为流
  rpc SayHiIOStream(stream Req) returns (stream Res); //双向流
}

流式传入(客户端流)

rpc SayHiInStream(stream Req)returns (Res){};

服务端编写

func(s *service) SayHiInStream(serve hello_grpc.HelloService_SayHiInStreamServer)(err error) {
var res hello_grpc.Res
res.NameRes = "success"
for{
  if tem,err: = serve.Recv();
  err != nil {
    fmt.Println("传输完了", err) serve.SendAndClose(&res) break
  }
  else {
    fmt.Println(tem)
  }
}
return
}

func(grpc 服务结构体)方法名(grpc 给定好的一个流式回参server)(错误){ serve.Recv() 进行接收 判断是否 eof

然后 可以在最后关闭之前 用 sendAndClose传输res回去 }

客户端编写

inClient,_ := client.SayHiInStream(context.Background())
i:=1
for{
  inClient.Send(&hello_grpc.Req{Sex: "99",Name: "我是流"})
  time.Sleep(1 * time.Second)
  i++
    if i>10 {
      res, err := inClient.CloseAndRecv()
      fmt.Println(res.NameRes,err)
      break
    }
}

通过client上面的grpc提前建好的一个流方法

创建一个 client

然后对流进行写入操作 在准备关闭的时候 调用 closeAndRecv()关闭并且拿到服务端返回的信息

流式返回(服务端流)

rpc SayHiOutStream(Req)returns (stream Res){};

服务端

type server struct {
  pb.UnimplementedHelloServiceServer
}

func (s *server) SayHiOutStream(req *pb.Req, stream pb.HelloService_SayHiOutStreamServer) error {
  fmt.Printf("Received request from %s\n", req.Name)

  for i := 1; i <= 5; i++ {
    res := &pb.Res{
      Message: fmt.Sprintf("Hi, %s! This is message #%d", req.Name, i),
    }
    if err := stream.Send(res); err != nil {
      return err
    }
    time.Sleep(1 * time.Second) // 模拟延迟发送多个响应
  }

  return nil
}

func main() {
  lis, err := net.Listen("tcp", ":1019")
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }

  s := grpc.NewServer()
  pb.RegisterHelloServiceServer(s, &server{})
  log.Println("Server is running on port 1019...")
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}
定义的服务
  • 定义了一个 server 结构体,嵌套了 pb.UnimplementedHelloServiceServer,这是 gRPC 自动生成的代码中的占位实现。
  • 实现了 SayHiOutStream 方法:
    • 接收客户端的请求(*pb.Req)。
    • 循环发送 5 条响应消息给客户端,每条消息之间间隔 1 秒。
主函数
  • 创建一个 TCP 监听器,监听端口 1019
  • 初始化一个 gRPC 服务器实例,并将 server 注册到该实例中。
  • 启动 gRPC 服务器,开始处理客户端请求。

客户端

func main() {
  conn, err := grpc.Dial("localhost:1019", grpc.WithInsecure(), grpc.WithBlock())
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()

  client := pb.NewHelloServiceClient(client)

  req := &pb.Req{Name: "Alice"}
  stream, err := client.SayHiOutStream(context.Background(), req)
  if err != nil {
    log.Fatalf("could not send request: %v", err)
  }

  for {
    res, err := stream.Recv()
    if err != nil {
      break
    }
    fmt.Println("Received:", res.Message)
  }
}

流式出入(双向流)

rpc SayHiIOStream(stream Req)returns (stream Res){};

服务端

type server struct {
  pb.UnimplementedHelloServiceServer
}

func (s *server) SayHiIOStream(stream pb.HelloService_SayHiIOStreamServer) error {
  for {
    // 接收客户端的消息
    req, err := stream.Recv()
    if err != nil {
      return err
    }

    log.Printf("Received from client: %s", req.Name)

    // 回复一条消息给客户端
    res := &pb.Res{
      Message: fmt.Sprintf("Hi, %s! I'm the server.", req.Name),
    }

    if err := stream.Send(res); err != nil {
      return err
    }

    time.Sleep(500 * time.Millisecond) // 模拟处理延迟
  }
}

func main() {
  lis, err := net.Listen("tcp", ":1019")
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }

  s := grpc.NewServer()
  pb.RegisterHelloServiceServer(s, &server{})
  log.Println("Server is running on port 1019...")
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}
实现**SayHiIOStream**方法
func (s *server) SayHiIOStream(stream pb.HelloService_SayHiIOStreamServer) error {
  for {
    // 接收客户端的消息
    req, err := stream.Recv()
    if err != nil {
      return err
    }

    log.Printf("Received from client: %s", req.Name)

    // 回复一条消息给客户端
    res := &pb.Res{
      Message: fmt.Sprintf("Hi, %s! I'm the server.", req.Name),
    }

    if err := stream.Send(res); err != nil {
      return err
    }

    time.Sleep(500 * time.Millisecond) // 模拟处理延迟
  }
}
  • 这是一个无限循环,持续接收来自客户端的消息。
  • 每当接收到一条消息时,打印出客户端的名字,并构造一条回复消息发送回去。
  • 使用time.Sleep来模拟处理时间,让每条消息之间有短暂的延迟。

客户端

func main() {
  conn, err := grpc.Dial("localhost:1019", grpc.WithInsecure(), grpc.WithBlock())
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()

  client := pb.NewHelloServiceClient(conn)

  stream, err := client.SayHiIOStream(context.Background())
  if err != nil {
    log.Fatalf("could not open stream: %v", err)
  }

  // 启动 goroutine 监听服务端的响应
  go func() {
    for {
      res, err := stream.Recv()
      if err != nil {
        log.Printf("Error receiving: %v", err)
        break
      }
      fmt.Println("Server says:", res.Message)
    }
  }()

  // 模拟发送多个请求
  names := []string{"Alice", "Bob", "Charlie", "David"}
  for _, name := range names {
    req := &pb.Req{Name: name}
    if err := stream.Send(req); err != nil {
      log.Fatalf("could not send: %v", err)
    }
    fmt.Println("Sent:", name)
    time.Sleep(1 * time.Second)
  }

  // 关闭发送通道
  err = stream.CloseSend()
  if err != nil {
    log.Fatalf("could not close send: %v", err)
  }
}

启动 goroutine 监听服务端的响应
// 启动 goroutine 监听服务端的响应
  go func() {
    for {
      res, err := stream.Recv()
      if err != nil {
        log.Printf("Error receiving: %v", err)
        break
      }
      fmt.Println("Server says:", res.Message)
    }
  }()
  • 在后台启动一个 goroutine 持续监听从服务端发来的消息。
  • 每次接收到消息时打印出来。
发送请求
  names := []string{"Alice", "Bob", "Charlie", "David"}
  for _, name := range names {
    req := &pb.Req{Name: name}
    if err := stream.Send(req); err != nil {
      log.Fatalf("could not send: %v", err)
    }
    fmt.Println("Sent:", name)
    time.Sleep(1 * time.Second)
  }
  • 遍历一个名字列表,将每个名字作为请求发送给服务端。
  • 发送请求后打印一条确认信息,并等待一秒以模拟用户输入间隔。
关闭发送通道
 // 关闭发送通道
  err = stream.CloseSend()
  if err != nil {
    log.Fatalf("could not close send: %v", err)
  }
  • 当所有消息都发送完毕后,调用CloseSend()关闭客户端的发送部分,表示不会再发送更多数据。
  • 这使得服务端能够检测到客户端已完成发送,从而可以选择是否继续处理或关闭连接。