什么是gRPC

gRPC 是一个高性能、开源、通用的 RPC 框架,由 Google 推出,基于 HTTP2
协议标准设计开发,默认采用 Protocol Buffers 数据序列化协议,支持多种开发语言。 gRPC
提供了一种简单的方法来精确地定义服务,并且为客户端和服务端自动生成可靠的功能库。

gRPC技术栈

最底层为 TCPUnix 套接字协议,在此之上是 HTTP/2 协议的实现,然后在 HTTP/2 协议之上又构建了针对 Go 语言的 gRPC 核心库( gRPC 内核+解释器)。应用程序通过 gRPC
插件生成的 Stub 代码和 gRPC 核心库通信,也可以直接和 gRPC 核心库通信。

前置准备

安装 protoc

下面示例是在 mac环境 中安装。

1
2
3
4
5
# 安装
➜ ~ brew install protobuf
# 安装后查看版本
➜ ~ protoc --version
libprotoc 3.17.3

安装插件

安装插件的目的是为了将 protobuf 文件,生成 Go 代码

1
2
➜  ~ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
➜ ~ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

设置插件环境变量

1
➜  ~ export PATH="$PATH:$(go env GOPATH)/bin"

验证插件是否安装成功

1
2
3
4
5
6
7
# 查看protoc-gen-go版本
➜ ~ protoc-gen-go --version
protoc-gen-go v1.26.0

# 查看protoc-gen-go-grpc版本
➜ ~ protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.1.0

快速使用

定义 protobuf 文件

文件名: hello.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
syntax = "proto3";
package hello;

// 定义go生成后的包名
option go_package = "sever/hello";

// 定义入参
message Request {
string name = 1;
}

// 定义返回
message Response {
string result = 1;
}

// 定义接口
service UserService {
rpc Say(Request) returns (Response);
}

生成 GO 代码

1
2
# 同时生成hello.pb.go和hello_grpc.pb.go
➜ grpc protoc --go-grpc_out=. --go_out=. hello.proto

查看生成代码

  1. hello.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
108
109
110
111
112
113
114
115
116
117
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.17.3
// source: hello.proto

package hello

import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)

const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// 定义入参
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_hello_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}

func (x *Request) String() string {
return protoimpl.X.MessageStringOf(x)
}

func (*Request) ProtoMessage() {}

func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_hello_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}

// Deprecated: Use Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return file_hello_proto_rawDescGZIP(), []int{0}
}

func (x *Request) GetName() string {
if x != nil {
return x.Name
}
return ""
}

// 定义返回
type Response struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
}

func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_hello_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}

func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x)
}

func (*Response) ProtoMessage() {}

func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_hello_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}

// Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*Response) Descriptor() ([]byte, []int) {
return file_hello_proto_rawDescGZIP(), []int{1}
}

func (x *Response) GetResult() string {
if x != nil {
return x.Result
}
return ""
}
  1. hello_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
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.

package hello

import (
context "context"
"fmt"
grpc "google.golang.org/grpc"
)

// 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

// UserServiceClient is the client API for UserService 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 UserServiceClient interface {
Say(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}

type userServiceClient struct {
cc grpc.ClientConnInterface
}

func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}

func (c *userServiceClient) Say(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/hello.UserService/Say", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}

// UserServiceServer is the server API for UserService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
type UserServiceServer interface {
Say(context.Context, *Request) (*Response, error)
mustEmbedUnimplementedUserServiceServer()
}

// UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
type UnimplementedUserServiceServer struct {
}

func (UnimplementedUserServiceServer) Say(ctx context.Context, r *Request) (*Response, error) {
replay := &Response{Result: fmt.Sprintf("%s say hello world.", r.Name)}
return replay, nil
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}

// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to UserServiceServer will
// result in compilation errors.
type UnsafeUserServiceServer interface {
mustEmbedUnimplementedUserServiceServer()
}

func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv)
}

下载 grpc

1
➜  grpc go get -u google.golang.org/grpc

编写服务端代码

server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"go-advanced/grpc/sever/hello"
"google.golang.org/grpc"
"net"
)

func main() {
// 创建grpc服务
grpcServer := grpc.NewServer()
// 注册服务
hello.RegisterUserServiceServer(grpcServer, new(hello.UnimplementedUserServiceServer))
// 监听端口
listen, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("服务启动失败", err)
return
}
_ = grpcServer.Serve(listen)
}

编写客户端代码

client.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
package main

import (
"context"
"fmt"
"go-advanced/grpc/sever/hello"
"google.golang.org/grpc"
)

func main() {
// 建立链接
conn, err := grpc.Dial("127.0.0.1:1234", grpc.WithInsecure())
if err != nil {
fmt.Println("Dial err ", err)
return
}
// 延迟关闭链接
defer conn.Close()
// 实例化客户端
client := hello.NewUserServiceClient(conn)
// 发起请求
reply, err := client.Say(context.TODO(), &hello.Request{Name: "张三"})
if err != nil {
return
}
fmt.Println("返回:", reply.Result)
}

启动 & 请求

1
2
3
4
5
# 启动服务端
➜ grpc go run server.go
# 启动客户端
➜ grpc go run client.go
返回: 张三 say hello word!