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

gRPC-Go
Github

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

一、快速入门

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

1.1 先决条件

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中。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值