上一篇主要记录了RPC与gRPC的一些概念和环境搭建,这一篇主要记录Protocol Buffers的详细使用及gRPC四种数据流模式
Protocol Buffers
Protocol buffers提供了一种与语言无关、平台中立的可扩展机制,用于序列化数据结构,跟JSON类似,不过它比JSON体积更小、速度更快
1. Protocol Buffers类型
Protocol Buffers的数据类型和其它语言类似,有double、float、int类型等,一个message的结构如下。
syntax="proto3"
package tutorial;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
// 枚举类型
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
// 嵌套另外一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
// 数组
repeated PhoneNumber phones = 4;
// map类型
map<string, string> lang = 6;
// 引用第三方proto文件的message类型: import "google/protobuf/timestamp.proto";
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
Protocol Buffers与其它语言类型的映射关系
默认值
Protocol Buffers在解析message结构的时候,如果编码中不赋值的话,则将该message对象中的相应字段设置为该字段的默认值。
- string的默认值为"",
- bytes的默认值为空字节
- bool的默认值为false
- number的默认值为0
- enum的默认值为第一个定义的枚举值,该值必须为0
- message的默认值取决于编译的语言类型
2.option go_package
go_package有两部分组成,一部分是proto生成后的文件需要放在哪个目录下
, 另一部分是包名
例如:将proto生成的pb.go
文件放在相对于hello.proto文件的./hello/proto目录下
// hello_grpc/proto/hello.proto
syntax="proto3";
package hello;
option go_package="hello/proto;hello";
message Req {
string name = 1;
}
message Resp {
string message = 1;
}
service Greeter {
rpc SayHello (Req) returns(Resp);
}
执行protoc -I . hello.proto --go-grpc_out=. --go_out=.
后,生成的目录如下
如果想放根目录下的common目录,go_package="../../common/hello/proto;hello"
生成的目录就变成了
package的详细说明可以看官网的package说明
3. 引用其它proto文件
- 引入同一个包下的不同文件的proto
// grpcdemo/hello_grpc/proto/hello.proto
syntax="proto3";
package hello;
option go_package=".;hello";
// 引入ping.proto文件, 这里import不能使用相对路径,例如: "./ping.proto"
import "ping.proto";
message Req {
string name = 1;
}
message Resp {
string message = 1;
}
service Greeter {
rpc SayHello (Req) returns(Resp);
}
service PingPong {
rpc Ping(Empty) returns(Pong);
// grpcdemo/hello_grpc/proto/ping.proto
syntax="proto3";
package hello;
option go_package=".;hello";
message Empty{}
message Pong {
string name = 1;
}
在grpcdemo/hellogrpc/proto
目录下, 先后执行protoc -I ping.proto --go_out=. --go-grpc_out=.
和protoc -I hello.proto --go_out=. --go-grpc_out=.
即可
- 引入不同文件的不同包的proto
// grpcdemo/hello_grpc/proto/hello/hello.proto
syntax="proto3";
package hello;
option go_package="./;hello";
// 这里导入的是从项目根目录开始的绝对路径
import "grpcdemo/hello_grpc/proto/ping/ping.proto";
message Req {
string name = 1;
}
message Resp {
string message = 1;
}
service Greeter {
rpc SayHello (Req) returns(Resp);
}
//service PingPong {
// rpc Ping(ping.Empty) returns(ping.Pong);
//}
message PingPong{
optional ping.Pong pingtest = 1;
}
// grpcdemo/hello_grpc/proto/ping/ping.proto
syntax="proto3";
package ping;
option go_package=".;ping";
message Empty{}
message Pong {
string name = 1;
}
先在grpcdemo/hello_grpc/proto/ping
目录下执行protoc -I . ping.proto --go_out=. --go-grpc_out=.
生成ping.pb.go
和ping_grpc.pb.go
文件,然后在grpcdemo/hello_grpc/proto/hello
目录下执行protoc -I ../../../../ -I ./hello --go_out=. --go-grpc_out=. *.proto
需要注意的是-I
表示从项目根目录开始,一层一层找到项目的根目录执行
gRPC四种数据流模式
-
简单模式(Simple RPC)
-
服务端数据流模式(Server-side streaming RPC): 这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是股票交易
-
客户端数据流模式(Client-side stream RPC): 与服务端数据流模式相反,客户端源源不断的向服务端发送数据流,发送结束后,服务端返回一个相应。典型的例子是物联网终端向服务器报送数据
-
双向数据流模式(Bidirectional stream RPC): 客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是实现实时交互。典型的例子就是聊天机器人
实践
- 新建stream.proto文件
//stream_grpc/proto/stream.proto
syntax = "proto3";
package stream;
option go_package = "./;stream";
message StreamReqData {
string data = 1;
}
message StreamResData {
string data = 1;
}
service Greeter {
rpc GetStream(StreamReqData) returns(stream StreamResData); // 服务端流模式
rpc PostStream(stream StreamReqData) returns(StreamResData); // 客户端流模式
rpc AllStream(stream StreamReqData) returns(stream StreamResData); // 双向数据流模式
}
- 创建服务端
// stream_grpc/server/server.go
package main
import (
"fmt"
"google.golang.org/grpc"
stream "grpcdemo/stream_grpc/proto"
"net"
"sync"
"time"
)
const PORT = ":8888"
type server struct {
stream.UnimplementedGreeterServer
}
func (s *server) GetStream(req *stream.StreamReqData, res stream.Greeter_GetStreamServer) error {
i := 0
for {
i++
res.Send(&stream.StreamResData{
Data: fmt.Sprintf("%v", time.Now().Unix()),
})
time.Sleep(time.Second)
if i > 10 {
break
}
}
return nil
}
func (s *server) PostStream(cliStr stream.Greeter_PostStreamServer) error {
for {
if a, err := cliStr.Recv(); err != nil {
fmt.Println(err)
break
} else {
fmt.Println(a.Data)
}
}
return nil
}
func (s *server) AllStream(allStr stream.Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到客户端消息: " + data.Data)
}
}()
go func() {
defer wg.Done()
for {
allStr.Send(&stream.StreamResData{Data: "我是服务器"})
time.Sleep(time.Second)
}
}()
wg.Wait()
return nil
}
func main() {
s := grpc.NewServer()
stream.RegisterGreeterServer(s, &server{})
listener, err := net.Listen("tcp", PORT)
if err != nil {
panic(err)
}
s.Serve(listener)
}
- 创建客户端
// stream_grpc/client/client.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
stream "grpcdemo/stream_grpc/proto"
"sync"
"time"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
if err != nil {
return
}
client := stream.NewGreeterClient(conn)
//服务端数据流
res, _ := client.GetStream(context.Background(), &stream.StreamReqData{Data: "choi"})
for {
a, err := res.Recv()
if err != nil {
fmt.Println(err)
break
}
fmt.Println(a.Data)
}
// 客户端数据流
putS, _ := client.PostStream(context.Background())
i := 0
for {
i++
err := putS.Send(&stream.StreamReqData{
Data: fmt.Sprintf("choi %d", i),
})
if err != nil {
return
}
time.Sleep(time.Second)
if i > 10 {
break
}
}
// 双向模式
allStr, _ := client.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
for {
data, _ := allStr.Recv()
fmt.Println("收到服务器消息: " + data.Data)
}
}()
go func() {
defer wg.Done()
for {
allStr.Send(&stream.StreamReqData{Data: "我是客户端"})
time.Sleep(time.Second)
}
}()
wg.Wait()
}
分别启动server.go和client.go程序,可以看到如下
// client.go
...
1663997151
1663997152
EOF
收到服务器消息: 我是服务器
收到服务器消息: 我是服务器
收到服务器消息: 我是服务器
...
// server.go
...
choi 10
choi 11
收到客户端消息: 我是客户端
收到客户端消息: 我是客户端
收到客户端消息: 我是客户端
...