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

文章目录

  • [1. 服务定义](#1. 服务定义)
    • [1.1 一元RPC](#1.1 一元RPC)
    • [1.2 服务端流式RPC](#1.2 服务端流式RPC)
    • [1.3 客户端流式RPC](#1.3 客户端流式RPC)
    • [1.4 双向流式RPC](#1.4 双向流式RPC)
  • [2. 生成GRPC代码](#2. 生成GRPC代码)
  • [3. 构建逻辑代码](#3. 构建逻辑代码)
    • [3.1 Server端](#3.1 Server端)
    • [3.2 Client端](#3.2 Client端)
    • [3.3 一元RPC](#3.3 一元RPC)
    • [3.4 服务端流式RPC](#3.4 服务端流式RPC)
    • [3.5 客户端流式RPC](#3.5 客户端流式RPC)
    • [3.6 双向流式RPC](#3.6 双向流式RPC)

1. 服务定义

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

go 复制代码
service Route {
  // ...
}

1.1 一元RPC

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

go 复制代码
message SimpleRequest {
  string data = 1;
}

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

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

1.2 服务端流式RPC

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

go 复制代码
message SimpleRequest {
  string data = 1;
}

message StreamResponse{
  string stream_value = 1;
}

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

1.3 客户端流式RPC

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

go 复制代码
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

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

go 复制代码
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工具的安装和使用等,可以参照前面的教程。

go 复制代码
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端

go 复制代码
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端

go 复制代码
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

go 复制代码
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 复制代码
$ go run server.go                    
2023/01/04 23:13:59 :8080 net.Listing...

客户端实现代码为:

go 复制代码
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 复制代码
$ 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

服务端代码如下:

go 复制代码
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
}

客户端代码如下:

go 复制代码
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 复制代码
$ 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

go 复制代码
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)
   }
}

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

go 复制代码
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 复制代码
$ 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 复制代码
$ 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

go 复制代码
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)
   }
}

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

go 复制代码
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 复制代码
$ 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 复制代码
go run client.go                                                                                                                                                                                              1 ↵
2023/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
相关推荐
apocelipes1 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner5 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz10 天前
QML Hello World 入门示例
qt
xcyxiner13 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner14 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言