什么是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!