1、概述
1.1 什么是gRPC
RPC
的全称是Remote Procedure Call
,远程过程调用。RPC是一种协议,它实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。
而gRPC
又是什么呢?用官方的话来说:
A high-performance, open-source universal RPC framework
gRPC
是一个高性能的、开源的通用的RPC框架。
在gRPC
中,我们称调用方为client
,被调用方为server
。 跟其他的RPC
框架一样,gRPC
也是基于”服务定义“的思想。简单的来讲,就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个”服务定义“的过程中,我们描述了我们提供的服务的服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好了这些服务、这些方法之后,gRPC
会屏蔽底层的细节,client
只需要直接调用定义好的方法,就能拿到预期的返回结果。对于server
端来说,还需要实现我们定义的方法。同样的,gRPC
也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
你可以发现,在上面的描述过程中,所谓的”服务定义“,就跟定义接口的语义是很接近的。我更愿意理解为这是一种”约定“,双方约定好接口,然后server
实现这个接口,client
调用这个接口的代理对象。至于其他的细节,交给gRPC
。
此外,gRPC
还是语言无关的。你可以用C++作为服务端,使用Golang、Java等作为客户端。为了实现这一点,我们在”定义服务“和在编码和解码的过程中,应该是做到语言无关的。
如下图所示就是一个典型的RPC结构图。
通过上图可以看到gRPC
使用了Protocol Buffers
。本文不会展开来讲Protocol Buffers(详细proto语法参见:Golang使用Protobuf)
,你可以把他当成一个代码生成工具以及序列化工具。这个工具可以把我们定义的方法,转换成特定语言的代码。比如你定义了一种类型的参数,他会帮你转换成Golang
中的struct结构体
,你定义的方法,他会帮你转换成func函数
。此外,在发送请求和接受响应的时候,这个工具还会完成对应的编码和解码工作,将你即将发送的数据编码成gRPC
能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。
1.2 使用场景
- 低延时、高可用的分布式系统;
- 移动端与云服务端的通讯;
- 使用protobuf,独立于语言的协议,支持多语言之间的通讯;
- 可以分层扩展,如:身份验证,负载均衡,日志记录,监控等;
1.3 gRPC 与 RESTful API比较
特性 | gRPC | RESTful API |
规范 | 必须.proto | 可选 OpenAPI |
协议 | HTTP/2 任意版本的 | HTTP 协议 |
有效载荷 | Protobuf(小、二进制) | JSON(大、易读) |
浏览器支持 | 否(需要 grpc-web) | 是 |
流传输 | 客户端、服务端、双向 | 客户端、服务端 |
代码生成 | 是 | OpenAPI + 第三方工具 |
2、环境配置
2.1 安装配置protocol buffers和protoc-gen-go
2.2 获取gRPC
1 |
go get google.golang.org/grpc
|
这一步安装的是gRPC
的核心库。
2.3 获取protoc-gen-go-grpc
1 |
go install google.golang.org/grpc/cmd/protoc-gen- go -grpc@latest
|
安装protoc-gen-go-grpc用于.proto-->***_grpc.pb.go。
3、gRPC入门示例
在开始开发之前,先说说我们的目标。
在这个grpc-practice
项目中,我希望实现一个功能,客户端可以发送消息给服务端,服务端收到消息后,返回响应给客户端。
项目结构如下:
注意: 这是整个项目所有文件生成完后的结构,所有.proto和.go文件都是在3.1及其后步骤生成的,go.mod内容如下:
+ View Code3.1 定义服务
正如前面所说的,在开发server
与client
之前,我们需要先定义服务。这里直接粘贴下示例proto文件内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 文件路径grpc-practice/pkg/proto/message.proto
syntax = "proto3" ;
// 这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,../pb代表在当前目录的上一级pb目录中生成,message代表了生成的go文件的包名是message。
option go_package = "../pb;pb" ;
message MessageResponse {
string responseSomething = 1;
}
message MessageRequest {
string saySomething = 1;
}
service MessageSender {
rpc Send(MessageRequest) returns (MessageResponse) {}
}
|
很容易可以看出,我们定义了一个service,称为MessageSender
,这个服务中有一个rpc方法,名为Send
。这个方法会发送一个MessageRequest
,然后返回一个MessageResponse
。
接着在grpc-practice/pkg/proto目录下执行如下命令:
1 2 |
protoc --go_out=. message.proto
protoc -- go -grpc_out=. message.proto
|
这两条命令会grpc-practice/pkg/pb目录中生成message.pb.go、message_grpc.pb.go这两个文件。在这两个文件中,包含了我们定义方法的go语言实现,也包含了我们定义的请求与相应的go语言实现。
简单来讲,就是protoc-gen-go
已经把你定义的语言无关的message.proto
转换为了go语言的代码,以便server
和client
直接使用。
注意:在网上的一些教程中,有这样的生成方式:
1 |
protoc --go_out=plugins=grpc:. message.proto
|
这种生成方式,使用的就是github
版本的protoc-gen-go
,而目前这个项目已经由Google接管了。并且,如果使用这种生成方式的话,并不会生成上图中的xxx_grpc.pb.go
与xxx.pb.go
两个文件,只会生成xxx.pb.go
这种文件。
3.2 服务端
3.2.1 实现服务定义的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 文件路径grpc-practice/pkg/serviceImpl/MessageSenderServerImpl.go
package serviceImpl
import (
"context"
"grpc-practice/pkg/pb"
"log"
)
type MessageSenderServerImpl struct {
*pb.UnimplementedMessageSenderServer
}
func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {
log.Println( "receive message:" , request.GetSaySomething())
resp := &pb.MessageResponse{}
resp.ResponseSomething = "roger that!"
return resp, nil
}
|
很容易可以看出,MessageSenderServerImpl实现了MessageSenderServer接口,并实现定义。也就是说,这一部分是需要我们在Server
端实现这个send方法的。
3.2.2 gRPC服务端注册定义的服务并监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 文件路径grpc-practice/pkg/service/main.go
package main
import (
"google.golang.org/grpc"
"grpc-practice/pkg/pb"
"grpc-practice/pkg/serviceImpl"
"log"
"net"
)
func main() {
srv := grpc.NewServer()
pb.RegisterMessageSenderServer(srv,serviceImpl.MessageSenderServerImpl{})
listener, err := net.Listen( "tcp" , ":8002" )
if err != nil {
log.Fatalf( "failed to listen: %v" , err)
}
err = srv.Serve(listener)
if err != nil {
log.Fatalf( "failed to serve: %v" , err)
}
}
|
很容易可以看出,我们在这一部分创建了一个grpcServer,然后注册了我们的Service,在注册函数的第二个参数中,我们传进去了一个MessageSenderServerImpl实例。
监听过程跟golang的web服务器是很像的,也是创建Handler,然后对端口进行监听,监听8002
端口的TCP连接,然后启动服务器。
至此,服务端开发完毕。
3.3 客户端
在客户端中,我们应该先与server
端建立连接,然后才能够调用各种方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 文件路径grpc-practice/pkg/client/main.go
package main
import (
"context"
"google.golang.org/grpc"
"grpc-practice/pkg/pb"
"log"
)
func main() {
conn, err := grpc.Dial( "127.0.0.1:8002" ,grpc.WithInsecure())
if err != nil {
log.Fatalf( "did not connect: %v" , err)
}
defer conn.Close()
client := pb.NewMessageSenderClient(conn)
resp, err := client.Send(context.Background(), &pb.MessageRequest{SaySomething: "hello world!" })
if err != nil {
log.Fatalf( "could not greet: %v" , err)
}
log.Println( "receive message:" , resp.GetResponseSomething())
}
|
以上代码,就是跟本地的8002
端口建立连接。然后,本地创建了一个client,然后直接调用我们之前定义好的Send方法,就可以实现我们需要的逻辑了,调用server段的方法和调用本地方法一样方便。
server
端和client
端都跑起来,你会看到这样的画面:
至此,gRPC示例成功。
4、gPRC 生成代码为什么会有 UnimplementedMessageSenderServer和 mustEmbedUnimplementedMessageSenderServer{}
这里先粘一下message_grpc.pb.go文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# 文件路径grpc-practice/pkg/pb/message_grpc.pb.go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.20.1
// source: message.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// MessageSenderClient is the client API for MessageSender service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MessageSenderClient interface {
Send(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error)
}
type messageSenderClient struct {
cc grpc.ClientConnInterface
}
func NewMessageSenderClient(cc grpc.ClientConnInterface) MessageSenderClient {
return &messageSenderClient{cc}
}
func (c *messageSenderClient) Send(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error) {
out := new(MessageResponse)
err := c.cc.Invoke(ctx, "/MessageSender/Send" , in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MessageSenderServer is the server API for MessageSender service.
// All implementations must embed UnimplementedMessageSenderServer
// for forward compatibility
type MessageSenderServer interface {
Send(context.Context, *MessageRequest) (*MessageResponse, error)
mustEmbedUnimplementedMessageSenderServer()
}
// UnimplementedMessageSenderServer must be embedded to have forward compatible implementations.
type UnimplementedMessageSenderServer struct {
}
func (UnimplementedMessageSenderServer) Send(context.Context, *MessageRequest) (*MessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented" )
}
func (UnimplementedMessageSenderServer) mustEmbedUnimplementedMessageSenderServer() {}
// UnsafeMessageSenderServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MessageSenderServer will
// result in compilation errors.
type UnsafeMessageSenderServer interface {
mustEmbedUnimplementedMessageSenderServer()
}
func RegisterMessageSenderServer(s grpc.ServiceRegistrar, srv MessageSenderServer) {
s.RegisterService(&MessageSender_ServiceDesc, srv)
}
func _MessageSender_Send_Handler(srv interface {}, ctx context.Context, dec func ( interface {}) error, interceptor grpc.UnaryServerInterceptor) ( interface {}, error) {
in := new(MessageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageSenderServer).Send(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/MessageSender/Send" ,
}
handler := func (ctx context.Context, req interface {}) ( interface {}, error) {
return srv.(MessageSenderServer).Send(ctx, req.(*MessageRequest))
}
return interceptor(ctx, in, info, handler)
}
// MessageSender_ServiceDesc is the grpc.ServiceDesc for MessageSender service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MessageSender_ServiceDesc = grpc.ServiceDesc{
ServiceName: "MessageSender" ,
HandlerType: (*MessageSenderServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Send" ,
Handler: _MessageSender_Send_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "message.proto" ,
}
|
通过UnsafeMessageSenderServer接口注释可以这个是为了向前兼容,具体是怎么做呢?
其实是这样子:
因为在 protoc 帮我们生成的 .pb.go 文件中定义了 UnimplementedXxxServer 结构体,并且 *UnimplementedXxxServer 实现了 XxxServer 这个接口。所以我们写一个 XxxServerImpl,嵌入 *UnimplementedXxxServer 类型,也就实现了 XxxServer 这个接口。
1 2 3 4 5 6 7 |
type MessageSenderServerImpl struct {
*pb.UnimplementedMessageSenderServer
}
func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {
......
}
|
如果以后 .proto 协议中的 service 有变更,增加、删除函数或者修改原来的函数,重新生成 .pb.go 文件后,XxxServer 这个接口也相应的变化了,由于protoc 帮我们重新生成了 UnimplementedXxxServer,它一定是实现了 XxxServer 这个接口的,所以我们的 XxxServerImpl 也是实现了 XxxServer 这个接口的。只不过在调用我们重写的 rpc 方法时,调用的可能就是嵌入类型 *pb.UnimplementedMessageSenderServer它的 Send了。(这涉及到 golang 的嵌入和组合:现有一个 Struct ,嵌入了一个其他类型,用外部类型调用某方法,如果外部类型包含了符合要求的接口实现,它的方法将会被使用。否则,通过方法提升,内部类型的接口实现可以直接被外部类型使用。)
5、总结
简单的来讲,我们在*.proto
文件中定义了方法,然后在server
端实现定义的rpc方法的具体逻辑,在client端调用这个方法。
对于其他的部分,由proto buffer
负责对Golang
中存储的数据结构与rpc
传输中的数据进行转换,grpc
负责封装所有的逻辑。
参考:https://zhuanlan.zhihu.com/p/258879142
参考:https://blog.csdn.net/canon_in_d_major/article/details/108135724
标签:pb,err,示例,gRPC,Send,Golang,grpc,go From: https://www.cnblogs.com/beatle-go/p/17988004