gRPC-Go
Github
gRPC的Go实现:一个高性能、开源、通用的RPC框架,将移动和HTTP/2放在首位。有关更多信息,请参阅Go gRPC文档,或直接进入快速入门。
本指南通过一个简单的工作示例让您开始在Go中使用gRPC。
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"
示例代码是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
在examples/helloworld
目录中:
1、编译并执行服务器代码:
$ go run greeter_server/main.go
2、从不同的终端,编译并执行客户端代码,以查看客户端输出:
$ go run greeter_client/main.go
Greeting: Hello world
恭喜你!您刚刚使用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;
}
记得保存文件!
在使用新的服务方法之前,需要重新编译更新后的.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.go
和helloworld/helloworld_grpc.pb.go
文件,包含:
HelloRequest
和HelloReply
消息类型的代码。您已经重新生成了服务器和客户机代码,但是仍然需要在示例应用程序的人工编写部分实现和调用新方法。
打开 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
目录中执行以下命令:
$ go run greeter_server/main.go
$ go run greeter_client/main.go --name=Alice
您将看到以下输出:
Greeting: Hello Alice
Greeting: Hello again Alice
本教程提供了一个基本的Go程序员使用gRPC的介绍。
通过这个例子,你将学习如何:
.proto
文件中定义服务。本文假设您已经阅读了gRPC介绍,并且熟悉协议缓冲区。请注意,本教程中的示例使用了协议缓冲区语言的proto3版本:您可以在proto3语言指南和Go生成代码指南中找到更多信息。
我们的示例是一个简单的路线应用程序,它允许客户机获取关于其路由特性的信息,创建其路由的摘要,并与服务器和其他客户机交换路由信息,例如流量更新。
有了gRPC,我们可以在一个.proto
文件中定义我们的服务,并用gRPC支持的任何语言生成客户端和服务器,这些客户端和服务器可以在从大型数据中心的服务器到你自己的平板电脑的环境中运行——不同语言和环境之间的所有复杂通信都由gRPC为你处理。我们还获得了使用协议缓冲区的所有优点,包括高效的序列化、简单的IDL和简单的接口更新。
您应该已经安装了生成客户端和服务器接口代码所需的工具——如果您还没有安装,请参阅快速入门的先决条件部分以获得安装说明。
示例代码是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
我们的第一步(您将从gRPC介绍中了解到)是使用协议缓冲区定义gRPC (service
)以及方法请求(request
)和响应(response
)类型。完整的.proto
文件,请参见 routeguide/route_guide.proto。
要定义一个服务,你需要在你的.proto
文件中指定一个命名服务:
service RouteGuide {
...
}
然后在服务定义中定义rpc
方法,指定它们的请求和响应类型。gRPC允许你定义四种服务方法,它们都在RouteGuide
服务中使用:
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
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) {}
stream
关键字放在请求类型之前,可以指定客户端流方法。// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
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;
}
接下来,我们需要从.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
其中包含以下内容
RouteGuide
服务中定义的方法调用。RouteGuide
服务中定义的方法。首先,让我们看看如何创建RouteGuide
服务器。如果您只对创建gRPC客户端感兴趣,您可以跳过这一节,直接进入创建客户端(尽管您可能会觉得这很有趣!)
要使我们的RouteGuide
服务发挥作用,有两个部分:
您可以在server/server.go中找到示例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 {
...
}
...
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
可以返回给客户端了。
现在让我们来看一个流式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
}
正如您所看到的,这次我们不是在方法参数中获得简单的请求和响应对象,而是获得了一个请求对象(客户端想要在其中查找Feature
的Rectangle
)和一个特殊的RouteGuide_ListFeaturesServer
对象来编写响应。
在该方法中,我们填充了我们需要返回的尽可能多的Feature
对象,并使用RouteGuide_ListFeaturesServer
的Send()
方法将它们写入RouteGuide_ListFeaturesServer
。最后,就像在简单RPC中一样,我们返回一个nil
错误来告诉gRPC我们已经完成了响应的编写。如果在这个调用中发生任何错误,我们返回一个非nil
错误;gRPC层将其转换为适当的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_RecordRouteServer
的Recv()
方法反复将客户端的请求读入到请求对象(在本例中是Point
),直到没有更多的消息:服务器需要在每次调用后检查Recv()
返回的错误。如果这是nil
,流仍然是好的,它可以继续读取;如果是io.EOF
消息流已经结束,服务器可以返回它的RouteSummary
。如果它有任何其他值,我们将“原样”返回错误,以便gRPC层将其转换为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()
,因为它要写入多个响应。尽管每一方总是按照它们被写入的顺序获得另一方的消息,但客户端和服务器都可以以任何顺序读取和写入消息——流完全独立地运行。
一旦我们实现了所有的方法,我们还需要启动一个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()
。
在本节中,我们将讨论如何为RouteGuide
服务创建Go客户端。您可以在grpc-go/examples/route_guide/client/client.go中看到完整的示例客户端代码。
为了调用服务方法,我们首先需要创建一个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)
现在让我们看看如何调用服务方法。注意,在gRPC-Go
中,RPC以阻塞/同步模式运行,这意味着RPC调用等待服务器响应,并将返回响应或错误。
调用简单的RPC GetFeature
几乎和调用本地方法一样简单。
feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906})
if err != nil {
...
}
如您所见,我们调用了前面得到的存根上的方法。在我们的方法参数中,我们创建并填充一个请求协议缓冲对象(在我们的例子中是Point)。我们还传递一个context.Context
对象,它允许我们在必要时更改RPC的行为,例如超时/取消正在运行的RPC。如果调用没有返回错误,那么我们可以从服务器的第一个返回值读取响应信息。
log.Println(feature)
在这里,我们调用服务器端流方法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_ListFeaturesClient
的Recv()
方法反复读入服务器对响应协议缓冲区对象(在本例中是Feature
)的响应,直到没有更多的消息:客户端需要在每次调用后检查Recv()
返回的错误err。如果为nil
,则流仍然正常,可以继续读取;如果是io.EOF
则消息流已经结束;否则,必须有一个RPC错误,该错误通过err
传递。
客户端流方法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(
)的第一个返回值将是一个有效的服务器响应。
最后,让我们看看双向流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()
方法。尽管每一方总是按照它们被写入的顺序获得另一方的消息,但客户端和服务器都可以以任何顺序读取和写入消息——流完全独立地运行。
在examples/route_guide
目录下执行以下命令:
1、运行服务器:
$ go run server/server.go
2、从另一个终端运行客户端:
$ go run client/client.go
An overview of gRPC authentication in Go using Application Layer Transport Security (ALTS).
ALTS (Application Layer Transport Security)是谷歌公司开发的一种相互认证和传输加密系统。它用于保护谷歌基础设施中的RPC通信。ALTS类似于TLS,但经过了设计和优化,以满足谷歌生产环境的需要。欲了解更多信息,请参阅ALTS白皮书。
gRPC中的ALTS具有以下特点:
gRPC用户可以配置他们的应用程序,使用ALTS作为传输安全协议,只需很少的代码行。
注意,如果应用程序运行在谷歌云平台上,ALTS是完全功能的。ALTS可以在任何具有可插拔ALTS握手服务( ALTS handshaker service)的平台上运行。
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))
gRPC服务器可以使用ALTS凭据来允许客户端连接到它们,如下所示:
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/alts"
)
altsTC := alts.NewServerCreds(alts.DefaultServerOptions())
server := grpc.NewServer(grpc.Creds(altsTC))
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))
在成功连接时,对等信息(例如,客户端的服务帐户)存储在AltsContext中。gRPC为客户端授权检查提供了一个实用程序库。假设服务器知道预期的客户机标识(例如,[email protected]
),它可以运行以下示例代码来授权传入的RPC。
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/alts"
)
err := alts.ClientAuthorizationCheck(ctx, []string{"[email protected]"})
API Reference
本页描述了grpc插件protoc-gen-go-grpc
在使用protoc
编译.proto
文件时生成的代码。
您可以在服务定义中找到如何在.proto
文件中定义gRPC服务。
线程安全:请注意,客户端RPC调用和服务器端RPC处理程序是线程安全的,意味着可以在并发的goroutine 上运行。但也要注意,对于单个流,输入和输出数据是双向的,但是是串行的;例如,单个流不支持并发读或并发写(但读和写是安全并发的)。
在服务器端,.proto
文件中的每个service Bar
都会产生如下函数:
func RegisterBarServer(s *grpc.Server, srv BarServer)
应用程序可以定义BarServer
接口的具体实现,并使用此函数将其注册到grpc.Server
实例(在启动服务器实例之前)。
这些方法在生成的服务接口上具有以下签名:
Foo(context.Context, *MsgA) (*MsgB, error)
在这种情况下,MsgA
是从客户端发送的protobuf
消息,MsgB
是从服务器端返回的protobuf
消息。
这些方法在生成的服务接口上具有以下签名:
Foo(*MsgA, <ServiceName>_FooServer) error
在这个上下文中,MsgA
是来自客户机的单个请求,
参数表示服务器到客户机的MsgB
消息流。
有一个嵌入的grpc.ServerStream
和以下接口:
type <ServiceName>_FooServer interface {
Send(*MsgB) error
grpc.ServerStream
}
服务器端处理程序可以通过该参数的send
方法向客户端发送protobuf
消息流。服务器到客户端流的流结束是由处理程序方法的return
引起的。
这些方法在生成的服务接口上具有以下签名:
Foo(<ServiceName>_FooServer) error
在这个上下文中,
既可用于读取客户端到服务器的消息流,也可用于发送单个服务器响应消息。
有一个嵌入的grpc.ServerStream
和以下接口:
type <ServiceName>_FooServer interface {
SendAndClose(*MsgA) error
Recv() (*MsgB, error)
grpc.ServerStream
}
服务器端处理程序可以在此参数上重复调用Recv
,以便从客户端接收完整的消息流。Recv
一旦到达流的末尾就返回(nil, io.EOF)
。通过在
参数上调用SendAndClose
方法来发送来自服务器的单个响应消息。注意SendAndClose
必须被调用一次且只能被调用一次。
这些方法在生成的服务接口上具有以下签名:
Foo(<ServiceName>_FooServer) error
在这种情况下,
可用于访问客户端到服务器消息流和服务器到客户端消息流。
有一个嵌入的grpc.ServerStream
和以下接口:
type <ServiceName>_FooServer interface {
Send(*MsgA) error
Recv() (*MsgB, error)
grpc.ServerStream
}
服务器端处理程序可以在此参数上重复调用Recv
,以便读取客户端到服务器的消息流。Recv
一旦到达客户端到服务器流的末尾就返回(nil, io.EOF)
。响应服务器到客户端消息流通过重复调用ServiceName>_FooServer
参数上的Send
方法来发送。服务器到客户端流的流结束由bidi(双向)方法处理程序的return
指示。
对于客户端使用,.proto
文件中的每个服务Bar
也会生成函数:func BarClient(cc *grpc.ClientConn) BarClient
,它返回BarClient
接口的具体实现(这个具体实现也存在于生成的.pb.go
文件中))。
这些方法在生成的客户端存根(stub)上具有以下签名:
(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (*MsgB, error)
在这种情况下,MsgA
是从客户机到服务器的单个请求,MsgB
包含从服务器发回的响应。
这些方法在生成的客户端存根上具有以下签名:
Foo(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)
在这个上下文中,
表示服务器到客户端的MsgB
消息流。
这个流有一个嵌入的grpc.ClientStream
和以下接口:
type <ServiceName>_FooClient interface {
Recv() (*MsgB, error)
grpc.ClientStream
}
当客户端调用存根上的Foo
方法时,流开始。然后客户端可以在返回的
流上重复调用Recv
方法,以读取服务器到客户端的响应流。一旦从服务器到客户端的流被完全读取,这个Recv
方法返回(nil, io.EOF)
。
这些方法在生成的客户端存根上具有以下签名:
Foo(ctx context.Context, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)
在这个上下文中,
表示客户端到服务器的MsgA
消息流。
有一个嵌入的grpc.ClientStream
和以下接口:
type <ServiceName>_FooClient interface {
Send(*MsgA) error
CloseAndRecv() (*MsgB, error)
grpc.ClientStream
}
当客户端调用存根上的Foo
方法时,流开始。然后客户端可以在返回的
流上重复调用Send
方法,以便发送客户端到服务器的消息流。这个流上的CloseAndRecv
方法必须被调用一次且仅被调用一次,以便关闭客户端到服务器的流并从服务器接收单个响应消息。
这些方法在生成的客户端存根上具有以下签名:
Foo(ctx context.Context, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)
在这个上下文中,
表示客户端到服务器和服务器到客户端消息流。
有一个嵌入的grpc.ClientStream
和以下接口:
type <ServiceName>_FooClient interface {
Send(*MsgA) error
Recv() (*MsgB, error)
grpc.ClientStream
}
当客户端调用存根上的Foo
方法时,流开始。然后客户端可以在返回的
流上重复调用Send
方法,以便发送客户端到服务器的消息流。客户端还可以在此流上重复调用Recv
,以便接收完整的服务器到客户端消息流。
服务器到客户端流的流结束由流的Recv
方法上的返回值(nil, io.EOF)
表示。客户端到服务器流的流结束可以从客户端通过调用流上的CloseSend
方法来指示。
当使用--go_out=plugins=grpc:
调用protoc
编译器时,proto package
到Go包的转换工作原理与使用protoc-gen-go
插件而不使用grpc
插件时相同。
例如,如果foo.proto
声明自己在package foo
中,那么生成的ffoo.pb.go
文件也将在Go包foo
中。