Go-grpc学习

Grpc学习

下载安装Protocol Buffers编译器

1
https://github.com/protocolbuffers/protobuf

下载后配置环境变量

安装Go protocol buffers插件

1
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

编写proto文件

1
2
3
4
5
6
7
8
9
10
11
syntax = "proto3";

option go_package = "./;hello";

package hello;

message Person {
string name = 1;
int32 age = 2;
string email = 3;
}
  • syntax -> 指定proto的版本号
  • option go_package -> “path;name” path表示生成的go文件存放地址 name表示生成的go文件所属的包名
  • package -> 指定proto的包名
  • message -> 生成的消息结构
  • 1 2 3只是字段的编号

生成proro的go文件

其中后面的参数是proto的源代码位置 /* 表示生成proto_src目录下的所有

./proto表示生成到改目录下

1
protoc --go_out=./proto ./proto_src/*

以上命令是 生成proto_src下所有的proto到proto下

数据类型

基本类型

.proto Type Go Type
double float64
float float32
int32 int32
int64 int64
uint32 uint32
uint64 uint64
sint32 int32
sint64 int64
fixed32 uint32
fixed64 uint64
sfixed32 int32
sfixed64 int64
bool bool
string string
bytes []byte

默认值

解析消息时,如果编码的消息不包含特定的元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数字类型,默认值为零。
  • 对于enums,默认值是第一个定义的 enum value,它必须是 0。
  • 对于消息字段,未设置该字段。它的确切值取决于语言。

重复字段的默认值为空(通常是相应语言的空列表)。

请注意,对于标量消息字段,一旦解析了消息,就无法判断该字段是显式设置为默认值(例如布尔值是否设置为false)还是根本没有设置:您应该牢记这一点定义消息类型时。例如,false如果您不希望在默认情况下也发生该行为,则不要在设置为时打开某些行为的布尔值。另请注意,如果标量消息字段设置为其默认值,则该值将不会在线上序列化。

枚举

在定义消息类型时,您可能希望其字段之一仅具有预定义的值列表之一。例如,假设您要corpus为每个 添加一个字段,SearchRequest其中语料库可以是UNIVERSALWEBIMAGESLOCALNEWS或。您可以通过在消息定义中添加一个非常简单的方法来做到这一点,并为每个可能的值添加一个常量。PRODUCTS``VIDEO``enum

在下面的示例中,我们添加了一个包含所有可能值的enum调用Corpus,以及一个 type 字段Corpus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}

如您所见,Corpus枚举的第一个常量映射到零:每个枚举定义都必须包含一个映射到零的常量作为其第一个元素。这是因为:

  • 必须有一个零值,以便我们可以使用 0 作为数字默认值
  • 零值必须是第一个元素,以便与第一个枚举值始终为默认值的proto2语义兼容。

您可以通过将相同的值分配给不同的枚举常量来定义别名。为此,您需要将allow_alias选项设置为true,否则协议编译器将在找到别名时生成错误消息。

不加option allow_alias = true; 枚举类型的值就不能一样,STARTED和RUNNING就不能同时为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message MyMessage1 {
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}
message MyMessage2 {
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
}

Map

1
2
3
message MyMessage3 {
map<string, string > map1 = 1;
}

数组

在数据类型前面加一个repeated关键字

1
2
3
message Test3 {
repeated string test = 1;
}

生成结构体

生成你的类

要生成需要使用.proto文件中定义的消息类型的 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 代码,您需要protoc.proto. 如果您尚未安装编译器,请下载软件包并按照 README 中的说明进行操作。对于 Go,您还需要为编译器安装一个特殊的代码生成器插件:您可以在 GitHub 上的golang/protobuf存储库中找到此插件和安装说明。

协议编译器调用如下:

1
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH指定.proto解析import指令时在其中查找文件的目录。如果省略,则使用当前目录。--proto_path多次传递该选项可以指定多个导入目录;他们将被按顺序搜索。-I=_IMPORT_PATH_可以用作 的简写形式--proto_path
  • 您必须提供一个或多个.proto文件作为输入。.proto可以一次指定多个文件。尽管文件是相对于当前目录命名的,但每个文件必须位于其中一个IMPORT_PATHs 中,以便编译器可以确定其规范名称。

定义服务(方法)

如果您想在 RPC(远程过程调用)系统中使用您的消息类型,您可以在一个.proto文件中定义一个 RPC 服务接口,并且协议缓冲区编译器将以您选择的语言生成服务接口代码和存根。因此,例如,如果你想定义一个 RPC 服务,它的方法接受你的SearchRequest并返回 a SearchResponse,你可以在你的.proto文件中定义它,如下所示:

1
2
3
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}

生成方法

1
protoc -I=./proto_src --go_out=./proto --go_opt=paths=source_relative --go-grpc_out=./proto --go-grpc_opt=paths=source_relative ./proto_src/*.proto

四种传输方式

正常方法

1
rpc Search(Person) returns (Person);

开启服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
listen, err := net.Listen("tcp", ":8888")
if err != nil {
log.Println("Listen err", err)
}
server := grpc.NewServer()
person.RegisterSearchServiceServer(server, &personServe{})
err = server.Serve(listen)
if err != nil {
log.Println("Serve err", err)
}
}

func (*personServe) Search(ctx context.Context, p *person.PersonReq) (*person.PersonRes, error) {
return &person.PersonRes{
Name: "拿到了传来的参数,姓名是" + p.Name,
Age: p.Age,
}, nil
}

开启客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
if err != nil {
log.Println("Dial", err)
}
defer conn.Close()
client := person.NewSearchServiceClient(conn)
res, err := client.Search(context.Background(), &person.PersonReq{Name: "我是zwj", Age: 18})
if err != nil {
log.Println("Search err", err)
}
fmt.Println(res)
}

流式输入正常输出

1
rpc SearchIn(stream Person) returns (Person);

服务端

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
func main() {
listen, err := net.Listen("tcp", ":8888")
if err != nil {
log.Println("Listen err", err)
}
server := grpc.NewServer()
person.RegisterSearchServiceServer(server, &personServe{})
err = server.Serve(listen)
if err != nil {
log.Println("Serve err", err)
}
}

func (*personServe) SearchIn(server person.SearchService_SearchInServer) error {
for {
req, err := server.Recv()
if err != nil {
server.SendAndClose(&person.PersonRes{
Name: "完成了",
})
break
}
fmt.Println(req)
}
return nil
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
if err != nil {
log.Println("Dial", err)
}
defer conn.Close()
client := person.NewSearchServiceClient(conn)
in, err := client.SearchIn(context.Background())
i := 0
for {
if i > 10 {
res, _ := in.CloseAndRecv()
fmt.Println(res)
break
}
time.Sleep(1 * time.Second)
err := in.Send(&person.PersonReq{Name: "我是进来的流信息"})
if err != nil {
return
}
i++
}
}

正常输入流式输出

1
rpc SearchOut(Person) returns (stream Person);

服务端

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
func main() {
listen, err := net.Listen("tcp", ":8888")
if err != nil {
log.Println("Listen err", err)
}
server := grpc.NewServer()
person.RegisterSearchServiceServer(server, &personServe{})
err = server.Serve(listen)
if err != nil {
log.Println("Serve err", err)
}
}

func (*personServe) SearchOut(req *person.PersonReq, server person.SearchService_SearchOutServer) error {
name := req.Name
fmt.Println("我接收到了请求,准备往回发送流消息")
fmt.Println("开始发送消息")
for i := 0; i < 10; i++ {
server.Send(&person.PersonRes{
Name: name,
})
fmt.Println("发送了", i+1, "条")
time.Sleep(1 * time.Second)
}
fmt.Println("发送完成")
return nil
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
if err != nil {
log.Println("Dial", err)
}
defer conn.Close()
client := person.NewSearchServiceClient(conn)
out, err := client.SearchOut(context.Background(), &person.PersonReq{Name: "我是发送给SearchOut的信息"})
if err != nil {
return
}
fmt.Println("开始接收")
for {
res, err := out.Recv()
if err != nil {
break
}
fmt.Println("接收到了->", res)
}
defer out.CloseSend()
fmt.Println("接收完毕")
}

双向流

1
rpc SearchIo(stream Person) returns (stream Person);

服务端

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
func main() {
listen, err := net.Listen("tcp", ":8888")
if err != nil {
log.Println("Listen err", err)
}
server := grpc.NewServer()
person.RegisterSearchServiceServer(server, &personServe{})
err = server.Serve(listen)
if err != nil {
log.Println("Serve err", err)
}
}

func (*personServe) SearchIo(server person.SearchService_SearchIoServer) error {
result := make(chan string)
go func() {
for {
fmt.Println("准备往客户端发送")
server.Send(&person.PersonRes{
Name: <-result,
})
fmt.Println("发送完毕")
time.Sleep(1 * time.Second)
}
}()
for {
fmt.Println("准备开始接收客户端的请求")
req, err := server.Recv()
if err != nil {
break
}
fmt.Println("接收到了客户端发来的请求->", req.Name)
result <- req.Name
}

return nil
}

客户端

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
func main() {
conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
if err != nil {
log.Println("Dial", err)
}
defer conn.Close()
client := person.NewSearchServiceClient(conn)

io, err := client.SearchIo(context.Background())
for {
fmt.Println("准备往服务端发送")
io.Send(&person.PersonReq{
Name: "zwj",
})
fmt.Println("发送完毕")
fmt.Println("准备开始接收服务端的返回")
res, err := io.Recv()
if err != nil {
break
}
fmt.Println("接收到了服务端返回的数据->", res.Name)

time.Sleep(1 * time.Second)
}
}


Go-grpc学习
https://www.gravity.wang/2022/06/04/grpc/
Author
Gravity
Posted on
June 4, 2022
Licensed under