本指南描述了使用protoc
编译.proto
文件时在protoc-gen-go
插件中使用grpc
插件生成代码。
在服务定义章节中查看如何在.proto
文件中定义一个服务。
线程安全:注意客户端RPC调用和服务端RPC处理都是线程安全的,这意味着能在并发的
goroutine
中运行。但是请注意,对于单个流来说,传入和传出数据是双向串行的,所以单个流不支持并发读取并发写入(但并发执行读入和写入是安全的)。
在服务端,.proto
文件中的每个服务都会生成如下函数:
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
:是从客户端发送的单个请求<ServiceName>_FooServer
:参数代表传送MsgB
消息的,从服务端到客户端的流<ServiceName>_FooServer
有一个嵌入的grpc.ServerStream
和下面的接口:
type <ServiceName>_FooServer interface {
Send(*MsgB) error
grpc.ServerStream
}
服务端的处理器可以通过这个参数的Send
方法发送protobuf
消息流到客户端。根据服务端处理器方法的返回结果来判断从服务端到客户端的流是否结束。
这些方法在生成服务端接口时具有如下签名:
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()方法能且仅能调用一次。
这些方法在生成服务端接口时具有如下签名:
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
(双向)方法处理器的返回的结果表示从服务器到客户端流的结束。
对于客户端的使用来说,在.proto
文件中的每个服务都会产生一下函数:
func BarClient(cc *grpc.ClientConn) BarClient
它返回BarClient
接口的具体实现(这个具体实现也存在于生成的.pb.go
文件中)。
这些方法在生成客户端stub
时具有如下签名:
Foo(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (*MsgB, error)
在这个上下文中:
MsgA
:从客户端到服务端的一个单一请求MsgB
:包含从服务端返回的响应信息这些方法在生成客户端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)
。
这些方法在生成客户端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()
方法能且仅能调用一次,以便能够在关闭从客户端到服务端流的同时接收从服务端返回的单个响应消息。
这些方法在生成客户端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()
方法指示。
使用 --go_out=plugins=grpc:
调用 protoc
编译器时,从 proto
包到 Go
包的转换与使用protoc-gen-go
插件而不使用grpc
插件的工作方式相同。
因此,例如,如果foo.proto
声明自己在包foo
中,那么生成的foo.pb.go
文件也将在Go包foo
中。