gRPC 基础(二)-- Go 语言版 gRPC-Go

2023-05-16

gRPC-Go
Github

gRPC的Go实现:一个高性能、开源、通用的RPC框架,将移动和HTTP/2放在首位。有关更多信息,请参阅Go gRPC文档,或直接进入快速入门。

一、快速入门

本指南通过一个简单的工作示例让您开始在Go中使用gRPC。

1.1 先决条件

  • Go:三个最新主要版本中的任意一个。
  • Protocol buffer:compiler, protoc, version 3.
  • 有关安装说明,请参阅协议缓冲区编译器安装。
  • 协议缓冲区编译器的Go插件:

1、使用以下命令安装Go的协议编译器插件:


go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

2、更新您的PATH,以便协议编译器可以找到插件:

# /etc/profile
$ export PATH="$PATH:$(go env GOPATH)/bin"

1.1.1 Get the example code

示例代码是grpc-go repo的一部分。
1、 将repo下载为zip文件并解压缩,或克隆repo:

$ git clone -b v1.52.0 --depth 1 https://github.com/grpc/grpc-go.git

2、 Change to the quick start example directory:

$ cd grpc-go/examples/helloworld

1.1.2 Run the example

examples/helloworld目录中:
1、编译并执行服务器代码:

$ go run greeter_server/main.go

2、从不同的终端,编译并执行客户端代码,以查看客户端输出:

$ go run greeter_client/main.go
Greeting: Hello world

在这里插入图片描述
恭喜你!您刚刚使用gRPC运行了一个客户机-服务器应用程序。

1.2 更新gRPC服务

在本节中,您将使用一个额外的服务器方法更新应用程序。gRPC服务是使用协议缓冲区(protocol buffers)定义的。要了解如何在.proto文件中定义服务的更多信息,请参阅基础教程。现在,所有你需要知道的是,服务器和客户端存根都有一个SayHello() RPC方法,它从客户端接受一个HelloRequest参数,并从服务器返回一个HelloReply,该方法是这样定义的:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

打开helloworld/helloworld.proto。并添加一个新的SayHelloAgain()方法,具有相同的请求和响应类型:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

记得保存文件!

1.3 重新生成gRPC代码

在使用新的服务方法之前,需要重新编译更新后的.proto文件。

仍然在examples/helloworld目录下时,运行以下命令:

$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

错误提示:protoc-gen-go-grpc: program not found or is not executable

需要安装以下gRPC gen插件:


go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

他将重新生成helloworld/helloworld.pb.gohelloworld/helloworld_grpc.pb.go文件,包含:

  • 用于填充、序列化和检索 HelloRequestHelloReply消息类型的代码。
  • 生成客户端和服务器代码。

1.4 更新并运行应用程序

您已经重新生成了服务器和客户机代码,但是仍然需要在示例应用程序的人工编写部分实现和调用新方法。

更新服务器

打开 greeter_server/main.go并添加以下函数:

func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}

更新客户端

打开 greeter_client/main.go,将以下代码添加到main()函数体的末尾:

r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())

运行

像以前那样运行客户机和服务器。在examples/helloworld目录中执行以下命令:

  1. Run the server:
$ go run greeter_server/main.go
  1. 从另一个终端运行客户端。这一次,添加一个名称作为命令行参数:
$ go run greeter_client/main.go --name=Alice

您将看到以下输出:

Greeting: Hello Alice
Greeting: Hello again Alice

在这里插入图片描述

二、基础教程

本教程提供了一个基本的Go程序员使用gRPC的介绍。

通过这个例子,你将学习如何:

  • .proto文件中定义服务。
  • 使用协议缓冲区编译器生成服务器和客户端代码。
  • 使用Go gRPC API为您的服务编写一个简单的客户端和服务器。

本文假设您已经阅读了gRPC介绍,并且熟悉协议缓冲区。请注意,本教程中的示例使用了协议缓冲区语言的proto3版本:您可以在proto3语言指南和Go生成代码指南中找到更多信息。

2.1 为什么使用gRPC?

我们的示例是一个简单的路线应用程序,它允许客户机获取关于其路由特性的信息,创建其路由的摘要,并与服务器和其他客户机交换路由信息,例如流量更新。

有了gRPC,我们可以在一个.proto文件中定义我们的服务,并用gRPC支持的任何语言生成客户端和服务器,这些客户端和服务器可以在从大型数据中心的服务器到你自己的平板电脑的环境中运行——不同语言和环境之间的所有复杂通信都由gRPC为你处理。我们还获得了使用协议缓冲区的所有优点,包括高效的序列化、简单的IDL和简单的接口更新

2.2 设置

您应该已经安装了生成客户端和服务器接口代码所需的工具——如果您还没有安装,请参阅快速入门的先决条件部分以获得安装说明。

2.3 获取示例代码

示例代码是grpc-go repo的一部分。

1、将repo下载为zip文件并解压缩,或克隆repo:

$ git clone -b v1.52.0 --depth 1 https://github.com/grpc/grpc-go

2、切换到示例目录:

$ cd grpc-go/examples/route_guide

2.4 定义服务

我们的第一步(您将从gRPC介绍中了解到)是使用协议缓冲区定义gRPC (service )以及方法请求(request )和响应(response )类型。完整的.proto文件,请参见 routeguide/route_guide.proto。

要定义一个服务,你需要在你的.proto文件中指定一个命名服务:

service RouteGuide {
   ...
}

然后在服务定义中定义rpc方法,指定它们的请求和响应类型。gRPC允许你定义四种服务方法,它们都在RouteGuide服务中使用:

  • 一个简单的RPC,其中客户端使用存根向服务器发送请求并等待响应返回,就像普通的函数调用一样。
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
  • 服务器端流RPC,客户端向服务器发送请求并获取流以读取消息序列。客户端从返回的流中读取,直到没有更多的消息。正如您在我们的示例中所看到的,您通过将stream关键字放在响应类型之前来指定服务器端流方法
// Obtains the Features available within the given Rectangle.  Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
  • 客户端流RPC,客户端写入消息序列并将它们发送到服务器,同样使用提供的流。一旦客户端完成了消息的写入,它就会等待服务器读取所有消息并返回响应。通过将stream关键字放在请求类型之前,可以指定客户端流方法
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
  • 双向流RPC,其中双方使用读写流发送消息序列。这两个流是独立运行的,因此客户端和服务器可以按照它们喜欢的任何顺序进行读写:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息,然后再写入消息,或者其他读写组合。每个流中的消息顺序保持不变。可以通过在请求和响应之前放置stream关键字来指定这种类型的方法
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

我们的.proto文件还包含了我们的服务方法中使用的所有请求和响应类型的协议缓冲消息类型定义——例如,这里是Point消息类型:

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

2.5 生成客户端和服务器代码

接下来,我们需要从.proto服务定义中生成gRPC客户端和服务器接口。我们使用协议缓冲编译器协议和一个特殊的gRPC Go插件来做到这一点。这与我们在快速入门中所做的类似。

$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    routeguide/route_guide.proto

执行此命令会在routeguide目录下生成以下文件:

  • route_guide.pb.go 其中包含用于填充、序列化和检索请求和响应消息类型的所有协议缓冲区代码。
  • route_guide_grpc.pb.go 其中包含以下内容
    • 一种接口类型(或存根,stub),供客户端使用RouteGuide服务中定义的方法调用。
    • 一种供服务器实现的接口类型,同样使用RouteGuide服务中定义的方法。

2.6 创建服务器

首先,让我们看看如何创建RouteGuide服务器。如果您只对创建gRPC客户端感兴趣,您可以跳过这一节,直接进入创建客户端(尽管您可能会觉得这很有趣!)

要使我们的RouteGuide服务发挥作用,有两个部分:

  • 实现从服务定义生成的服务接口:完成服务的实际“工作”。
  • 运行gRPC服务器监听来自客户端的请求,并将它们分派到正确的服务实现。

您可以在server/server.go中找到示例RouteGuide服务器。让我们仔细看看它是如何工作的。

2.6.1 实现 RouteGuide

正如你所看到的,我们的服务器有一个routeGuideServer结构体类型,它实现了生成的RouteGuideServer 接口:

type routeGuideServer struct {
        ...
}
...

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
        ...
}
...

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}
...

2.6.1.1 简单的RPC

routeGuideServer实现了我们所有的服务方法。让我们先看看最简单的RPC类型,GetFeature,它只是从客户端获取一个Point,并从它的数据库中返回相应Feature信息。

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
  for _, feature := range s.savedFeatures {
    if proto.Equal(feature.Location, point) {
      return feature, nil
    }
  }
  // No feature was found, return an unnamed feature
  return &pb.Feature{Location: point}, nil
}

该方法被传递给RPC一个上下文对象和客户端的Point协议缓冲区请求,它返回一个带有响应信息的Feature协议缓冲区对象和error。在这个方法中,我们用适当的信息填充Feature,然后return 它和一个nil错误,告诉gRPC我们已经完成了RPC的处理,Feature可以返回给客户端了。

2.6.1.2 服务器端流RPC

现在让我们来看一个流式RPCs。ListFeatures是一个服务器端流RPC,所以我们需要将多个feature发送回客户端。

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
  for _, feature := range s.savedFeatures {
    if inRange(feature.Location, rect) {
      if err := stream.Send(feature); err != nil {
        return err
      }
    }
  }
  return nil
}

正如您所看到的,这次我们不是在方法参数中获得简单的请求和响应对象,而是获得了一个请求对象(客户端想要在其中查找FeatureRectangle)和一个特殊的RouteGuide_ListFeaturesServer对象来编写响应。

在该方法中,我们填充了我们需要返回的尽可能多的Feature对象,并使用RouteGuide_ListFeaturesServerSend()方法将它们写入RouteGuide_ListFeaturesServer。最后,就像在简单RPC中一样,我们返回一个nil错误来告诉gRPC我们已经完成了响应的编写。如果在这个调用中发生任何错误,我们返回一个非nil错误;gRPC层将其转换为适当的RPC状态并发送到网络上。

2.6.1.3 客户端流RPC

现在让我们看一些更复杂的东西:客户端流方法RecordRoute,我们从客户端获得一个Point流,并返回一个带有关于他们行程信息的RouteSummary。正如您所看到的,这次该方法根本没有请求参数。相反,它获得一个RouteGuide_RecordRouteServer流,服务器可以使用它来读取和写入消息——它可以使用它的Recv()方法接收客户端消息,并使用它的SendAndClose()方法返回它的单个响应。

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
  var pointCount, featureCount, distance int32
  var lastPoint *pb.Point
  startTime := time.Now()
  for {
    point, err := stream.Recv()
    if err == io.EOF {
      endTime := time.Now()
      return stream.SendAndClose(&pb.RouteSummary{
        PointCount:   pointCount,
        FeatureCount: featureCount,
        Distance:     distance,
        ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
      })
    }
    if err != nil {
      return err
    }
    pointCount++
    for _, feature := range s.savedFeatures {
      if proto.Equal(feature.Location, point) {
        featureCount++
      }
    }
    if lastPoint != nil {
      distance += calcDistance(lastPoint, point)
    }
    lastPoint = point
  }
}

在方法体中,我们使用RouteGuide_RecordRouteServerRecv()方法反复将客户端的请求读入到请求对象(在本例中是Point),直到没有更多的消息:服务器需要在每次调用后检查Recv()返回的错误。如果这是nil,流仍然是好的,它可以继续读取;如果是io.EOF消息流已经结束,服务器可以返回它的RouteSummary。如果它有任何其他值,我们将“原样”返回错误,以便gRPC层将其转换为RPC状态。

2.6.1.4 双向流RPC

最后,让我们看看双向流RPC RouteChat()

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
  for {
    in, err := stream.Recv()
    if err == io.EOF {
      return nil
    }
    if err != nil {
      return err
    }
    key := serialize(in.Location)
                ... // look for notes to be sent to client
    for _, note := range s.routeNotes[key] {
      if err := stream.Send(note); err != nil {
        return err
      }
    }
  }
}

这一次,我们得到了一个RouteGuide_RouteChatServer流,与我们的客户端流示例一样,该流可用于读写消息。然而,这一次我们通过方法的stream 返回值,而客户端仍在向他们的消息流写入消息。

这里读写的语法与我们的客户端流方法非常相似,除了服务器使用流的Send()方法而不是SendAndClose(),因为它要写入多个响应。尽管每一方总是按照它们被写入的顺序获得另一方的消息,但客户端和服务器都可以以任何顺序读取和写入消息——流完全独立地运行。

2.6.2 启动服务器

一旦我们实现了所有的方法,我们还需要启动一个gRPC服务器,以便客户端可以实际使用我们的服务。下面的代码片段展示了我们如何为RouteGuide服务做到这一点:

flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
  log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
...
grpcServer := grpc.NewServer(opts...)
pb.RegisterRouteGuideServer(grpcServer, newServer())
grpcServer.Serve(lis)

要构建并启动服务器,我们:

1、使用lis, err := net.Listen(...)指定我们想用来监听客户端请求的端口。
2、使用grpc.NewServer(...)创建一个gRPC服务器实例
3、向gRPC服务器注册我们的服务实现。
4、使用我们的端口详细信息在服务器上调用Serve()来进行阻塞等待,直到进程被杀死或调用Stop()

2.7 创建客户端

在本节中,我们将讨论如何为RouteGuide服务创建Go客户端。您可以在grpc-go/examples/route_guide/client/client.go中看到完整的示例客户端代码。

2.7.1 创建存根(stub)

为了调用服务方法,我们首先需要创建一个gRPC通道(channel )来与服务器通信。我们通过将服务器地址和端口号传递给grpc.Dial()来创建它,如下所示:

var opts []grpc.DialOption
...
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
  ...
}
defer conn.Close()

当服务需要时,您可以使用DialOptions grpc.Dial中设置认证凭据(例如TLS、GCE凭据或JWT凭据)RouteGuide服务不需要任何凭证。

一旦设置了gRPC通道,我们就需要一个客户端存根(client stub)来执行 RPCs。我们使用由示例.proto文件生成的pb包提供的NewRouteGuideClient方法来获取它。

client := pb.NewRouteGuideClient(conn)

2.7.2 调用服务方法

现在让我们看看如何调用服务方法。注意,在gRPC-Go中,RPC以阻塞/同步模式运行,这意味着RPC调用等待服务器响应,并将返回响应或错误。

2.7.2.1 简单的RPC

调用简单的RPC GetFeature几乎和调用本地方法一样简单。

feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906})
if err != nil {
  ...
}

如您所见,我们调用了前面得到的存根上的方法。在我们的方法参数中,我们创建并填充一个请求协议缓冲对象(在我们的例子中是Point)。我们还传递一个context.Context对象,它允许我们在必要时更改RPC的行为,例如超时/取消正在运行的RPC。如果调用没有返回错误,那么我们可以从服务器的第一个返回值读取响应信息。

log.Println(feature)

2.7.2.2 服务器端流RPC

在这里,我们调用服务器端流方法ListFeatures,它将返回一个地理Feature流。如果你已经读过创建服务器,其中一些可能看起来很熟悉 —— 流RPCs 在两边以类似的方式实现。

rect := &pb.Rectangle{ ... }  // initialize a pb.Rectangle
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
  ...
}
for {
    feature, err := stream.Recv()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
    }
    log.Println(feature)
}

与在简单RPC中一样,我们向方法传递一个上下文和一个请求。但是,我们得到的不是一个响应对象,而是RouteGuide_ListFeaturesClient的一个实例。客户端可以使用RouteGuide_ListFeaturesClient流来读取服务器的响应。

我们使用RouteGuide_ListFeaturesClientRecv()方法反复读入服务器对响应协议缓冲区对象(在本例中是Feature)的响应,直到没有更多的消息:客户端需要在每次调用后检查Recv()返回的错误err。如果为nil,则流仍然正常,可以继续读取;如果是io.EOF则消息流已经结束;否则,必须有一个RPC错误,该错误通过err传递。

2.7.2.3 客户端流RPC

客户端流方法RecordRoute与服务器端方法类似,不同之处是我们只向该方法传递一个上下文并返回一个RouteGuide_RecordRouteClient流,我们可以使用它来写入和读取消息。

// Create a random number of random points
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {
  points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
stream, err := client.RecordRoute(context.Background())
if err != nil {
  log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {
  if err := stream.Send(point); err != nil {
    log.Fatalf("%v.Send(%v) = %v", stream, point, err)
  }
}
reply, err := stream.CloseAndRecv()
if err != nil {
  log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v", reply)

RouteGuide_RecordRouteClient有一个Send()方法,我们可以使用它向服务器发送请求。一旦我们使用Send()完成了将客户端的请求写入流,我们需要在流上调用CloseAndRecv()来让 gRPC 知道我们已经完成了写入并期待收到响应。我们从CloseAndRecv()返回的err中获取RPC状态。如果状态为nil,则CloseAndRecv()的第一个返回值将是一个有效的服务器响应。

2.7.2.4 双向流RPC

最后,让我们看看双向流RPC RouteChat()。与RecordRoute的情况一样,我们只向该方法传递一个上下文对象并获得一个流,可以用来写入和读取消息。但是,这一次我们通过方法的stream 返回值,而服务器仍在向它们的消息流写入消息。

stream, err := client.RouteChat(context.Background())
waitc := make(chan struct{})
go func() {
  for {
    in, err := stream.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 at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
  }
}()
for _, note := range notes {
  if err := stream.Send(note); err != nil {
    log.Fatalf("Failed to send a note: %v", err)
  }
}
stream.CloseSend()
<-waitc

这里读写的语法与我们的客户端流方法非常相似,只是我们在完成调用后使用流的CloseSend()方法。尽管每一方总是按照它们被写入的顺序获得另一方的消息,但客户端和服务器都可以以任何顺序读取和写入消息——流完全独立地运行。

2.8 试试吧!

examples/route_guide目录下执行以下命令:
1、运行服务器:

$ go run server/server.go

2、从另一个终端运行客户端:

$ go run client/client.go

在这里插入图片描述

三、ALTS 身份验证

An overview of gRPC authentication in Go using Application Layer Transport Security (ALTS).

3.1 概述

ALTS (Application Layer Transport Security)是谷歌公司开发的一种相互认证和传输加密系统。它用于保护谷歌基础设施中的RPC通信。ALTS类似于TLS,但经过了设计和优化,以满足谷歌生产环境的需要。欲了解更多信息,请参阅ALTS白皮书。

gRPC中的ALTS具有以下特点:

  • 创建使用ALTS作为传输安全协议的 gRPC 服务器和客户端。
  • ALTS 连接端到端保护隐私和完整性。
  • 应用程序可以访问对等服务帐户等对等信息。
  • 支持客户端授权和服务器授权。
  • 最小的代码更改以启用ALTS。

gRPC用户可以配置他们的应用程序,使用ALTS作为传输安全协议,只需很少的代码行。

注意,如果应用程序运行在谷歌云平台上,ALTS是完全功能的。ALTS可以在任何具有可插拔ALTS握手服务( ALTS handshaker service)的平台上运行。

3.2 gRPC客户端使用ALTS传输安全协议

gRPC客户端可以使用ALTS凭证连接到服务器,如下所示的代码摘录:

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

altsTC := alts.NewClientCreds(alts.DefaultClientOptions())
conn, err := grpc.Dial(serverAddr, grpc.WithTransportCredentials(altsTC))

3.3 gRPC服务器使用ALTS传输安全协议

gRPC服务器可以使用ALTS凭据来允许客户端连接到它们,如下所示:

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

altsTC := alts.NewServerCreds(alts.DefaultServerOptions())
server := grpc.NewServer(grpc.Creds(altsTC))

3.4 服务器授权 (Server Authorization)

gRPC具有使用ALTS的内置服务器授权支持。使用ALTS的gRPC客户端可以在建立连接之前设置预期的服务器服务帐户。然后,在握手结束时,服务器授权保证服务器标识与客户机指定的服务帐户之一匹配。否则,连接失败。

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

clientOpts := alts.DefaultClientOptions()
clientOpts.TargetServiceAccounts = []string{expectedServerSA}
altsTC := alts.NewClientCreds(clientOpts)
conn, err := grpc.Dial(serverAddr, grpc.WithTransportCredentials(altsTC))

3.5 客户端授权 (Client Authorization)

在成功连接时,对等信息(例如,客户端的服务帐户)存储在AltsContext中。gRPC为客户端授权检查提供了一个实用程序库。假设服务器知道预期的客户机标识(例如,foo@iam.gserviceaccount.com),它可以运行以下示例代码来授权传入的RPC。

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/alts"
)

err := alts.ClientAuthorizationCheck(ctx, []string{"foo@iam.gserviceaccount.com"})

四、API

API Reference

五、生成的代码参考

本页描述了grpc插件protoc-gen-go-grpc在使用protoc编译.proto文件时生成的代码。

您可以在服务定义中找到如何在.proto文件中定义gRPC服务。

线程安全:请注意,客户端RPC调用和服务器端RPC处理程序是线程安全的,意味着可以在并发的goroutine 上运行。但也要注意,对于单个流,输入和输出数据是双向的,但是是串行的;例如,单个流不支持并发读或并发写(但读和写是安全并发的)。

5.1 生成的服务器接口上的方法

在服务器端,.proto文件中的每个service Bar都会产生如下函数:

func RegisterBarServer(s *grpc.Server, srv BarServer)

应用程序可以定义BarServer接口的具体实现,并使用此函数将其注册到grpc.Server实例(在启动服务器实例之前)。

5.1.1 一元运算方法(Unary methods)

这些方法在生成的服务接口上具有以下签名:

Foo(context.Context, *MsgA) (*MsgB, error)

在这种情况下,MsgA是从客户端发送的protobuf消息,MsgB是从服务器端返回的protobuf消息。

5.1.2 Server-streaming方法

这些方法在生成的服务接口上具有以下签名:

Foo(*MsgA, <ServiceName>_FooServer) error

在这个上下文中,MsgA是来自客户机的单个请求,<ServiceName>_FooServer参数表示服务器到客户机的MsgB消息流。

<ServiceName>_FooServer有一个嵌入的grpc.ServerStream和以下接口:

type <ServiceName>_FooServer interface {
	Send(*MsgB) error
	grpc.ServerStream
}

服务器端处理程序可以通过该参数的send方法向客户端发送protobuf消息流。服务器到客户端流的流结束是由处理程序方法的return 引起的。

5.1.3 Client-streaming方法

这些方法在生成的服务接口上具有以下签名:

Foo(<ServiceName>_FooServer) error

在这个上下文中,<ServiceName>_FooServer既可用于读取客户端到服务器的消息流,也可用于发送单个服务器响应消息。

<ServiceName>_FooServer有一个嵌入的grpc.ServerStream和以下接口:

type <ServiceName>_FooServer interface {
	SendAndClose(*MsgA) error
	Recv() (*MsgB, error)
	grpc.ServerStream
}

服务器端处理程序可以在此参数上重复调用Recv,以便从客户端接收完整的消息流。Recv一旦到达流的末尾就返回(nil, io.EOF)。通过在<ServiceName>_FooServer参数上调用SendAndClose方法来发送来自服务器的单个响应消息。注意SendAndClose必须被调用一次且只能被调用一次。

5.1.4 Bidi-streaming方法

这些方法在生成的服务接口上具有以下签名:

Foo(<ServiceName>_FooServer) error

在这种情况下,<ServiceName>_FooServer可用于访问客户端到服务器消息流和服务器到客户端消息流。<ServiceName>_FooServer有一个嵌入的grpc.ServerStream和以下接口:

type <ServiceName>_FooServer interface {
	Send(*MsgA) error
	Recv() (*MsgB, error)
	grpc.ServerStream
}

服务器端处理程序可以在此参数上重复调用Recv,以便读取客户端到服务器的消息流。Recv一旦到达客户端到服务器流的末尾就返回(nil, io.EOF)。响应服务器到客户端消息流通过重复调用ServiceName>_FooServer参数上的Send方法来发送。服务器到客户端流的流结束由bidi(双向)方法处理程序的return指示。

5.2 生成的客户机接口上的方法

对于客户端使用,.proto文件中的每个服务Bar也会生成函数:func BarClient(cc *grpc.ClientConn) BarClient,它返回BarClient接口的具体实现(这个具体实现也存在于生成的.pb.go文件中))。

5.2.1 一元运算方法

这些方法在生成的客户端存根(stub)上具有以下签名:

(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (*MsgB, error)

在这种情况下,MsgA是从客户机到服务器的单个请求,MsgB包含从服务器发回的响应。

5.2.2 Server-Streaming方法

这些方法在生成的客户端存根上具有以下签名:

Foo(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)

在这个上下文中,<ServiceName>_FooClient表示服务器到客户端的MsgB消息流。
这个流有一个嵌入的grpc.ClientStream和以下接口:

type <ServiceName>_FooClient interface {
	Recv() (*MsgB, error)
	grpc.ClientStream
}

当客户端调用存根上的Foo方法时,流开始。然后客户端可以在返回的<ServiceName>_FooClient流上重复调用Recv方法,以读取服务器到客户端的响应流。一旦从服务器到客户端的流被完全读取,这个Recv方法返回(nil, io.EOF)

5.2.3 Client-Streaming方法

这些方法在生成的客户端存根上具有以下签名:

Foo(ctx context.Context, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)

在这个上下文中,<ServiceName>_FooClient表示客户端到服务器的MsgA消息流。

<ServiceName>_FooClient有一个嵌入的grpc.ClientStream和以下接口:

type <ServiceName>_FooClient interface {
	Send(*MsgA) error
	CloseAndRecv() (*MsgB, error)
	grpc.ClientStream
}

当客户端调用存根上的Foo方法时,流开始。然后客户端可以在返回的<ServiceName>_FooClient流上重复调用Send方法,以便发送客户端到服务器的消息流。这个流上的CloseAndRecv方法必须被调用一次且仅被调用一次,以便关闭客户端到服务器的流并从服务器接收单个响应消息。

5.2.4 Bidi-Streaming方法

这些方法在生成的客户端存根上具有以下签名:

Foo(ctx context.Context, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)

在这个上下文中,<ServiceName>_FooClient表示客户端到服务器和服务器到客户端消息流。
<ServiceName>_FooClient有一个嵌入的grpc.ClientStream和以下接口:

type <ServiceName>_FooClient interface {
	Send(*MsgA) error
	Recv() (*MsgB, error)
	grpc.ClientStream
}

当客户端调用存根上的Foo方法时,流开始。然后客户端可以在返回的<SericeName>_FooClient流上重复调用Send方法,以便发送客户端到服务器的消息流。客户端还可以在此流上重复调用Recv,以便接收完整的服务器到客户端消息流。

服务器到客户端流的流结束由流的Recv方法上的返回值(nil, io.EOF)表示。客户端到服务器流的流结束可以从客户端通过调用流上的CloseSend 方法来指示。

5.3 包和命名空间

当使用--go_out=plugins=grpc:调用protoc 编译器时,proto package到Go包的转换工作原理与使用protoc-gen-go插件而不使用grpc插件时相同。

例如,如果foo.proto声明自己在package foo中,那么生成的ffoo.pb.go文件也将在Go包foo中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gRPC 基础(二)-- Go 语言版 gRPC-Go 的相关文章

随机推荐

  • 【解决linux下连接向日葵失败或连接之后断开的解决方案】

    解决linux下连接向日葵失败或连接之后断开的解决方案 linux在软件中搜索lightdm桌面管理器并安装即可 xff01
  • 机器学习推荐系统评价指标之AUC

    机器学习推荐系统评价指标之AUC 综述AUC的计算过程AUC的优势 综述 AUC是机器学习模型中常见评价指标 xff0c 在推荐系统中也十分常见 和常见的评价指标Acc xff0c P xff0c R相比 xff0c AUC具备一定的优势
  • 多线程访问同步方法情况

    文章目录 1 多线程访问同步方法1 1 两个线程同时访问一个对象的同步方法1 1 1 代码演示1 1 2 运行结果 1 2 两个线程访问的是两个对象的同步方法1 2 1 代码演示1 2 2 运行结果 1 3 两个线程访问的是synchron
  • 剑指 Offer 33. 二叉搜索树的后序遍历序列

    题目描述 xff1a 输入一个整数数组 xff0c 判断该数组是不是某二叉搜索树的后序遍历结果 如果是则返回 true xff0c 否则返回 false 假设输入的数组的任意两个数字都互不相同 参考以下这颗二叉搜索树 xff1a 5 2 6
  • 求解空间两个三维坐标系之间的变换矩阵

    三维刚体变换模型 即旋转 平移矩阵 RT矩阵 的估计方法 原理简单阐述 只要算出变换矩阵 就可以算出A坐标系的一个点P在坐标系B里的对应点坐标 即 T为3x3的转换矩阵 t 为3x1的位移变换向量 这里点坐标均为3x1的列向量 非齐次形式
  • Ubuntu下网络调试助手 NetAssist

    近期在ubuntu下开发一个网络相关的程序 之前在windows上开发时 xff0c 一直使用NetAssist这个小工具 xff0c 简洁实用 所以就安装了一个对应版本的网络调试助手 NetAssist 下载地址 xff1a 链接 xff
  • 程序员裸辞三个月,终于拿到大厂offer!网友:不应该!

    一个行业发展成熟 xff0c 必定会重新洗牌 xff0c 就像朝代的更替一样 xff0c 现在互联网发展就是遇到了这样的瓶颈期 xff0c 出现了衰退 xff0c 就形成大家口中所说的 互联网寒冬 但是有技术的人哪里怕过寒冬 xff0c 所
  • HttpUtils 用于进行网络请求的工具类

    用于进行网络请求的工具类 xff0c 可进行get xff0c post两种请求 xff0c 值得一提的是这个utils给大家提供了一个回调接口 xff0c 方便获取下载文件的进度 span class hljs keyword impor
  • deepin系统

    https www uc23 net xinwen 76259 html 据介绍 xff0c 深度操作系统 xff08 deepin xff09 自 2015 年开始 xff0c 就放弃基于 Ubuntu 作为上游 xff0c 选择 Ubu
  • 学习日志2

    这几天一直在思考如何解决摄像头与vins与fast planner如何相结合再应用的问题 因为摄像头是因特尔的D435i xff0c 于是决定在gazebo上实现D435i的仿真 由于D435I版本较新 xff0c 因此github上基本没
  • 学习日志3

    这几天准备用分别用ego planner与fast planner进行飞行仿真 本来准备在双系统的ubuntu上安装ego planner xff08 之前ubuntu上已经安装过vins fusion vins mono与fast pla
  • 学习日志5

    最近老师让我阅读了一篇新文章 xff0c 文章标题如下图 文章通过解决时间分配问题以及通过模型预测轮廓同时控制问题控制 xff08 MPCC xff09 优化能够使四旋翼无人机找到最优轨迹 xff0c 可以快速地避障 xff0c 速度甚至可
  • 万字长文 | 阿里大佬 ssp offer 的后台服务器开发学习路线

    前言 小北去年经历春秋招 xff0c 拿到了不少大厂 offer xff0c 其中包括 sp ssp 等 xff0c 感觉在复习准备校招上还是有一定方法的 xff0c 因为我自己是 Linux C C 43 43 路线 所以这一篇的主题是
  • 看完谷歌大佬的刷题笔记, 我直接手撕了200道 Leetcode 算法题

    如果你刷leetcode觉得吃力 那么一定需要这份谷歌大佬的leetcode刷题笔记 在这里推荐一个谷歌大佬的刷题笔记 每一道题的题解都写得非常清楚 作者在美国卡内基梅隆大学攻读硕士学位时 xff0c 为了准备实习秋招 xff0c 他从夏天
  • JVM中的栈区域

    一 栈 xff1a 在JVM中也叫栈内存 xff0c 主要负责java程序的运行 xff0c 栈在线程创建时被创建 xff0c 栈时线程私有的 xff0c 也即每一个线程都有自己的栈空间 xff0c 线程之间的运行不受影响 相互独立 二 栈
  • 初步认识ADRC(自抗扰控制)与应用

    这是一个目录 ADRC的基本原理一 参考资料推荐二 为什么PID好 xff0c 以及 xff0c 为什么PID不够好1 为什么PID好 不依赖于模型的控制器2 为什么PID不够好 PID的缺点 三 ADRC给出的方案 如何保留PID的优点
  • 先进非线性控制方法 INDI 快速部署到PX4用于四旋翼控制(part2)

    目录 一 PX4 v11 的姿态控制解析1 角度环控制2 角速度环控制3 控制分配 二 简易INDI如何部署到PX41 获取角加速度 和 电机转速测量值 xff08 1 xff09 角加速度 xff08 2 xff09 转速 2 具体实现过
  • ubuntu18.04 cv2.VideoCapture无法读取视频

    源代码 xff1a span class token comment 读取视频 span span class token keyword import span cv2 video file span class token operat
  • 关于字符串结束标志‘\0‘的一些见解

    前言 本人是一个刚刚上路的IT新兵 菜鸟 分享一点自己的见解 如果有错误的地方欢迎各位大佬莅临指导 如果这篇文章可以帮助到你 劳请大家点赞转发支持一下 一 39 0 是什么 xff1f 0 是转义字符 xff0c 为了告诉编译器 0 是空字
  • gRPC 基础(二)-- Go 语言版 gRPC-Go

    gRPC Go Github gRPC的Go实现 一个高性能 开源 通用的RPC框架 xff0c 将移动和HTTP 2放在首位 有关更多信息 xff0c 请参阅Go gRPC文档 xff0c 或直接进入快速入门 一 快速入门 本指南通过一个