gRPC-Go基础(3)基础gRPC服务

1. 服务定义

前面说过,proto中定义消息结构体的关键字是message,同样,定义服务的关键字是service。

service Route {
  // ...
}

1.1 一元RPC

一元RPC(Unary RPC)的客户端就像调用本地函数一样地向服务端发送请求,并且等待服务端的返回。

message SimpleRequest {
  string data = 1;
}

message SimpleResponse {
  int32 code = 1;
  string data = 2;
}

service Route {
  rpc SimpleRoute(SimpleRequest) returns (SimpleResponse);
}

1.2 服务端流式RPC

客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息,比如数据下载的场景。

message SimpleRequest {
  string data = 1;
}

message StreamResponse{
  string stream_value = 1;
}

service Route {
  rpc ListValue(SimpleRequest) returns (stream StreamResponse){};
}

1.3 客户端流式RPC

相反,客户端会不断地向服务器发送数据流,比如客户端进行数据上传的场景。

message SimpleResponse {
  int32 code = 1;
  string data = 2;
}

message StreamRequest{
  string stream_data = 1;
}

service Route {
  rpc RouteList(stream StreamRequest) returns (SimpleResponse){};
}

1.4 双向流式RPC

客户端和服务端同时使用读写流去发送消息序列,两个流独立操作,可以同时发送和接收,比如双方会话的场景。

message StreamResponse{
  string stream_value = 1;
}

message StreamRequest{
  string stream_data = 1;
}

service Route {
  rpc DoubleSideStream(stream StreamRequest) returns (stream StreamResponse){};
}

2. 生成GRPC代码

以下指令可以生成xx.pb.go和xx_grpc.pb.go,至于protoc工具的安装和使用等,可以参照前面的教程。

protoc -I./proto \
--go_out=./genproto --go_opt paths=source_relative \
--go-grpc_out=./genproto --go-grpc_opt paths=source_relative \
./proto/*/*.proto

3. 构建逻辑代码

3.1 Server端

package main

import (
   "context"
   "log"
   "net"

   "github.com/IguoChan/proto-prj/genproto/simplepb"
   "google.golang.org/grpc"
)

func NewRouteServer() *Server {
   return &Server{}
}

type Server struct {
   simplepb.UnimplementedRouteServer
}

func (r *Server) SimpleRoute(ctx context.Context, request *simplepb.SimpleRequest) (*simplepb.SimpleResponse, error) {
   //TODO implement me
   panic("implement me")
}

func (r *Server) ListValue(request *simplepb.SimpleRequest, server simplepb.Route_ListValueServer) error {
   //TODO implement me
   panic("implement me")
}

func (r *Server) RouteList(server simplepb.Route_RouteListServer) error {
   //TODO implement me
   panic("implement me")
}

func (r *Server) DoubleSideStream(server simplepb.Route_DoubleSideStreamServer) error {
   //TODO implement me
   panic("implement me")
}

const (
   Address string = ":8080"
   Network string = "tcp"
)

func main() {
   // 监听本地端口
   listener, err := net.Listen(Network, Address)
   if err != nil {
      log.Fatalf("net.Listen err: %v", err)
   }
   log.Println(Address + " net.Listing...")

   // 新建gRPC的服务端实例
   grpcServer := grpc.NewServer()

   // 在gRPC服务器中注册我们的服务
   simplepb.RegisterRouteServer(grpcServer, NewRouteServer())

   // 起服务,阻塞等待
   err = grpcServer.Serve(listener)
   if err != nil {
      log.Fatalf("grpcServer.Serve err: %v", err)
   }
}

代码如上所示,其基本可以分为以下几个部分:

  • 在生成的xx_grpc.pb.go文件中,生成了定义的服务的RouteServer接口,我们需要一个对象来实现这个接口,就是以上的Server结构体,这个结构体需要包含Unimplemented开头的一个结构体,之后就可以选择性地实现服务接口;
  • 其次就是我们需要监听一个本地端口,用作grpc服务的端口;
  • 然后我们需要构建一个gRPC服务端的实例,并且在这个服务中注册我们的Server结构,这样后续client端的请求就可以打到Server实现的方法中;
  • 最后我们起服务等待就好了。

3.2 Client端

package main

import (
   "context"
   "io"
   "log"
   "strconv"

   "github.com/IguoChan/proto-prj/genproto/simplepb"
   "google.golang.org/grpc"
)

const (
   // Address 连接地址
   Address string = ":8080"
)

var grpcClient simplepb.RouteClient

func main() {
   // 连接服务器
   conn, err := grpc.Dial(Address, grpc.WithInsecure())
   if err != nil {
      log.Fatalf("net.Connect err: %v", err)
   }
   defer conn.Close()

   // 建立gRPC连接
   grpcClient = simplepb.NewRouteClient(conn)

   simpleRoute()      // 一元rpc
   listValue()        // 服务端流式rpc
   routeList()        // 客户端流式rpc
   doubleSideStream() // 双向流式rpc
}
...

Client端的代码如上:

  • 和Server端一样,xx_grpc.pb.go文件中同样生成了RouteClient接口,不同的是,在文件中,直接生成了这些接口的实现方法,接收器是routeClient,我们只需要调用这些方法即可,这部分,应该就是grpc文档中所言的stub;
  • 然后就是连接建立连接,并利用grpcClient去调用服务端的方法,实现远程调用。

接下来我们就具体看一下四种grpc调用方式的实现。

3.3 一元RPC

func (r *Server) SimpleRoute(ctx context.Context, request *simplepb.SimpleRequest) (*simplepb.SimpleResponse, error) {
   res := simplepb.SimpleResponse{
      Code: 200,
      Data: "hello " + request.Data + " " + time.Now().String(),
   }
   return &res, nil
}

服务端实现代码如上,运行后打印:

$ go run server.go                    
2023/01/04 23:13:59 :8080 net.Listing...

客户端实现代码为:

func simpleRoute() {
   // invoke the service with grpcClient
   res, err := grpcClient.SimpleRoute(context.Background(), &simplepb.SimpleRequest{Data: "I am iguochan"})
   if err != nil {
      log.Fatalf("SimpleRoute err: %+v", err)
   }
   log.Println(res)
}

运行后打印:

$ go run client.go
2023/01/04 23:15:44 code:200  data:"hello I am iguochan 2023-01-04 23:15:44.158069 +0800 CST m=+104.656427709"

可以发现,很容易就实现了grpc的简单调用。

3.4 服务端流式RPC

服务端代码如下:

func (r *Server) ListValue(request *simplepb.SimpleRequest, server simplepb.Route_ListValueServer) error {
   for n := 0; n < 5; n++ {
      // 向流中发送消息, 默认每次send送消息最大长度为`math.MaxInt32`bytes
      err := server.Send(&simplepb.StreamResponse{
         StreamValue: request.Data + strconv.Itoa(n),
      })
      if err != nil {
         return err
      }
   }
   return nil
}

客户端代码如下:

func listValue() {
   // 创建发送结构体
   req := simplepb.SimpleRequest{
      Data: "stream server grpc ",
   }
   // 调用我们的服务(ListValue方法)
   stream, err := grpcClient.ListValue(context.Background(), &req)
   if err != nil {
      log.Fatalf("Call ListStr err: %v", err)
   }
   for {
      //Recv() 方法接收服务端消息,默认每次Recv()最大消息长度为`1024*1024*4`bytes(4M)
      res, err := stream.Recv()
      // 判断消息流是否已经结束
      if err == io.EOF {
         break
      }
      if err != nil {
         log.Fatalf("ListStr get stream err: %v", err)
      }
      // 打印返回值
      log.Println(res.StreamValue)
   }
}

运行如下

$ go run client.go
2023/01/04 23:36:49 stream server grpc 0
2023/01/04 23:36:49 stream server grpc 1
2023/01/04 23:36:49 stream server grpc 2
2023/01/04 23:36:49 stream server grpc 3
2023/01/04 23:36:49 stream server grpc 4

值得注意的是,当客户端想结束客户端的发送时,可以使用stream.CloseSend()方法暂停服务端的发送,但是如果后来又调用了stream.Recv(),服务端又会继续发送数据。

3.5 客户端流式RPC

func (r *Server) RouteList(server simplepb.Route_RouteListServer) error {
   for {
      //从流中获取消息
      res, err := server.Recv()
      if err == io.EOF {
         //发送结果,并关闭
         return server.SendAndClose(&simplepb.SimpleResponse{
            Code: 200,
            Data: "hello " + time.Now().String(),
         })
      }
      if err != nil {
         return err
      }
      log.Println(res.StreamData)
   }
}

服务端的代码如上,客户端代码如下:

func routeList() {
   //调用服务端RouteList方法,获流
   stream, err := grpcClient.RouteList(context.Background())
   if err != nil {
      log.Fatalf("Upload list err: %v", err)
   }
   for n := 0; n < 5; n++ {
      //向流中发送消息
      err := stream.Send(&simplepb.StreamRequest{StreamData: "stream client rpc " + strconv.Itoa(n)})
      if err != nil {
         log.Fatalf("stream request err: %v", err)
      }
   }
   //关闭流并获取返回的消息
   res, err := stream.CloseAndRecv()
   if err != nil {
      log.Fatalf("RouteList get response err: %v", err)
   }
   log.Println(res)
}

服务端的打印如下:

$ go run server.go       
2023/01/04 23:43:07 :8080 net.Listing...
2023/01/04 23:43:28 stream client rpc 0
2023/01/04 23:43:28 stream client rpc 1
2023/01/04 23:43:28 stream client rpc 2
2023/01/04 23:43:28 stream client rpc 3
2023/01/04 23:43:28 stream client rpc 4

客户端的打印:

$ go run client.go
2023/01/04 23:43:28 code:200  data:"hello 2023-01-04 23:43:28.970596 +0800 CST m=+21.749100126"

3.6 双向流式RPC

func (r *Server) DoubleSideStream(server simplepb.Route_DoubleSideStreamServer) error {
   n := 1
   for {
      req, err := server.Recv()
      if err == io.EOF {
         return nil
      }
      if err != nil {
         return err
      }
      err = server.Send(&simplepb.StreamResponse{
         StreamValue: "from stream server answer: the " + strconv.Itoa(n) + " question is " + req.StreamData,
      })
      if err != nil {
         return err
      }
      n++
      log.Printf("from stream client question: %s", req.StreamData)
   }
}

服务端代码如上,客户端代码如下:

func doubleSideStream() {
   //调用服务端的Conversations方法,获取流
   stream, err := grpcClient.DoubleSideStream(context.Background())
   if err != nil {
      log.Fatalf("get conversations stream err: %v", err)
   }
   for n := 0; n < 5; n++ {
      err := stream.Send(&simplepb.StreamRequest{StreamData: "stream client rpc " + strconv.Itoa(n)})
      if err != nil {
         log.Fatalf("stream request err: %v", err)
      }
      res, err := stream.Recv()
      if err == io.EOF {
         break
      }
      if err != nil {
         log.Fatalf("Conversations get stream err: %v", err)
      }
      // 打印返回值
      log.Println(res.StreamValue)
   }
   //最后关闭流
   err = stream.CloseSend()
   if err != nil {
      log.Fatalf("Conversations close stream err: %v", err)
   }
}

服务端的打印如下:

$ go run server.go
2023/01/04 23:52:07 :8080 net.Listing...
2023/01/04 23:52:11 from stream client question: stream client rpc 0
2023/01/04 23:52:11 from stream client question: stream client rpc 1
2023/01/04 23:52:11 from stream client question: stream client rpc 2
2023/01/04 23:52:11 from stream client question: stream client rpc 3
2023/01/04 23:52:11 from stream client question: stream client rpc 4

客户端的打印:

go run client.go                                                                                                                                                                                              12023/01/04 23:52:11 from stream server answer: the 1 question is stream client rpc 0
2023/01/04 23:52:11 from stream server answer: the 2 question is stream client rpc 1
2023/01/04 23:52:11 from stream server answer: the 3 question is stream client rpc 2
2023/01/04 23:52:11 from stream server answer: the 4 question is stream client rpc 3
2023/01/04 23:52:11 from stream server answer: the 5 question is stream client rpc 4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值