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
其中语料库可以是UNIVERSAL
、WEB
、IMAGES
、LOCAL
、NEWS
或。您可以通过在消息定义中添加一个非常简单的方法来做到这一点,并为每个可能的值添加一个常量。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_PATH
s 中,以便编译器可以确定其规范名称。
定义服务(方法)
如果您想在 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) } }
|