09-Go生成代码指南

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

在服务定义章节中查看如何在.proto文件中定义一个服务。

线程安全:注意客户端RPC调用和服务端RPC处理都是线程安全的,这意味着能在并发的goroutine中运行。但是请注意,对于单个流来说,传入和传出数据是双向串行的,所以单个流不支持并发读取并发写入(但并发执行读入和写入是安全的)。

0.1. 生成服务端接口的方法

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

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

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

0.1.1. 简单方法

这些方法在生成服务端接口时具有如下签名:

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

在这个上下文中:

  • MsgA:是从客户端发送的protobuf消息
  • MsgB:是从服务端返回的protobuf消息

0.1.2. 服务端侧流数据方法

这些方法在生成服务端接口时具有如下签名:

Foo(*MsgA, <ServiceName>_FooServer) error

在这个上下文中:

  • MsgA:是从客户端发送的单个请求
  • <ServiceName>_FooServer:参数代表传送MsgB消息的,从服务端到客户端的流

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

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

服务端的处理器可以通过这个参数的Send方法发送protobuf消息流到客户端。根据服务端处理器方法的返回结果来判断从服务端到客户端的流是否结束。

0.1.3. 客户端侧流数据方法

这些方法在生成服务端接口时具有如下签名:

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()方法能且仅能调用一次

0.1.4. 双向流数据方法

这些方法在生成服务端接口时具有如下签名:

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(双向)方法处理器的返回的结果表示从服务器到客户端流的结束。

0.2. 生成客户端接口的方法

对于客户端的使用来说,在.proto文件中的每个服务都会产生一下函数:

 func BarClient(cc *grpc.ClientConn) BarClient

它返回BarClient接口的具体实现(这个具体实现也存在于生成的.pb.go文件中)。

0.2.1. 简单方法

这些方法在生成客户端stub时具有如下签名:

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

在这个上下文中:

  • MsgA:从客户端到服务端的一个单一请求
  • MsgB:包含从服务端返回的响应信息

0.2.2. 服务端侧流数据方法

这些方法在生成客户端stub时具有如下签名:

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
}

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

0.2.3. 客户端侧流数据方法

这些方法在生成客户端stub时具有如下签名:

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() (*MsgA, error)
    grpc.ClientStream
}

当客户端在stub上调用Foo方法时流开始。客户端会在返回的<ServiceName>_FooClient流上重复调用Send()方法,以便发送从客户端到服务端的消息流。这个流的CloseAndRecv()方法能且仅能调用一次,以便能够在关闭从客户端到服务端流的同时接收从服务端返回的单个响应消息。

0.2.4. 双向流数据方法

这些方法在生成客户端stub时具有如下签名:

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
}

当客户端在stub上调用Foo方法时流开始。客户端可以在返回的<ServiceName>_FooClient上重复调用Send()方法,以便发送从客户端到服务端的消息流。客户端也可以在这个流上重复调用Recv()方法,以便接收从服务端到客户端的消息流中全部消息。

服务端到客户端流的结束由流的Recv()方法上的返回值(nil,io.EOF)指示。

从客户端到服务端流的结束由客户端通过在流上调用CloseSend()方法指示。

0.3. 包和命名空间

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

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

上次修改: 14 April 2020