protoc语法详解及结合grpc定义服务

本文已被阅读过 Posted by 陌无崖 on 2019-08-04

导语

说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性。但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择。而今天总结的Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。本文主要以Golang语言进行介绍。

应用领域

数据通信、grpc通信、微服务。

准备环境

简单的例子

我们创建一个p.proto文件这个例子中message代表一个消息类型,在消息类型中有三个字段,这里不在多说,大家都明白。

1
2
3
4
5
6
7
syntax = "proto3";

message RequestParm {
string query = 1;
int32 pages = 2;
int32 article_page = 3;
}

现在我们运行一下,目录切换到这个文件的目录执行一下代码

1
protoc -I. --go_out=plugins=grpc:. p.proto

可以看到编译后出现了p.pb.go的文件,打开这个文件可以看到,有下面部分代码,我们的消息类型变成了一个结构体

1
2
3
4
5
6
7
8
type RequestParm struct {
Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
Pages int32 `protobuf:"varint,2,opt,name=pages,proto3" json:"pages,omitempty"`
ArticlePage int32 `protobuf:"varint,3,opt,name=article_page,json=articlePage,proto3" json:"article_page,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

在这个结构体中出现了一些基础的方法,如下

语法介绍

数据类型的对应

.proto Type Notes Go Type
double float64
float float32
int32 使用可变长度编码。 无效编码负数 - 如果您的字段可能具有负值, 请改用sint32。 int32
int64 使用可变长度编码。 无效编码负数 - 如果您的字段可能具有负值,请改用sint64。 int64
uint32 使用可变长度编码。 uint32
uint64 使用可变长度编码。 uint64
sint32 使用可变长度编码。 带符号的int值。 这些比常规的int32更有效地编码负数。 int32
sint64 使用可变长度编码。 带符号的int值。 这些比常规的int64更有效地编码负数。 int64
fixed32 总是四个字节。 如果值通常大于228,则比uint32效率更高。 uint32
fixed64 总是八个字节。 如果值通常大于256,则会比uint64更高效。 uint64
sfixed32 总是四个字节。 int32
sfixed64 总是八个字节。 int64
bool bool
string 字符串必须始终包含UTF-8编码或7位ASCII文本。 string
bytes 可能包含任何字节序列。 []byte

Repeated 字段【指针数组】

1
2
3
4
message ErrorStatus {
string message = 1;
repeated string details = 2;
}

枚举类型

1
2
3
4
5
6
7
8
9
10
message Bar {
string a = 15;
repeated RequestParm r = 4;
enum Data {
FIRST = 0;
SECOND = 1;
THIRD = 2;
}
Data data = 5;
}

编译之后的部分结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Bar_Data int32

const (
Bar_FIRST Bar_Data = 0
Bar_SECOND Bar_Data = 1
Bar_THIRD Bar_Data = 2
)
type Bar struct {
A string `protobuf:"bytes,15,opt,name=a,proto3" json:"a,omitempty"`
R []*RequestParm `protobuf:"bytes,4,rep,name=r,proto3" json:"r,omitempty"`
Data Bar_Data `protobuf:"varint,5,opt,name=data,proto3,enum=Bar_Data" json:"data,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

嵌套类型

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}

编译之后的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type SearchResponse_Result struct {
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
Snippets []string `protobuf:"bytes,3,rep,name=snippets,proto3" json:"snippets,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
type SearchResponse struct {
Results []*SearchResponse_Result `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

map类型

1
2
3
4
message SubMessage {
string sub = 1;
map<string, string> m = 4;
}

编译之后的结果

1
2
3
4
5
6
7
type SubMessage struct {
Sub string `protobuf:"bytes,1,opt,name=sub,proto3" json:"sub,omitempty"`
M map[string]string `protobuf:"bytes,4,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

Any类型

需要导入import "google/protobuf/any.proto";

1
2
3
4
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}

编译之后的结果

1
2
3
4
5
6
7
type ErrorStatus struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
Details []*any.Any `protobuf:"bytes,2,rep,name=details,proto3" json:"details,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

可以看到在编译之后的代码是一个*any.Any的指针数组,那么如何使用呢?按照Golang语言,这种类型代替的是interface{}类型 ,首先我们想看一下Any的源码

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
type Any struct {
// A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. The last segment of the URL's path must represent
// the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted).
//
// In practice, teams usually precompile into the binary all types that they
// expect it to use in the context of Any. However, for URLs which use the
// scheme `http`, `https`, or no scheme, one can optionally set up a type
// server that maps type URLs to message definitions as follows:
//
// * If no scheme is provided, `https` is assumed.
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
// value in binary format, or produce an error.
// * Applications are allowed to cache lookup results based on the
// URL, or have them precompiled into a binary to avoid any
// lookup. Therefore, binary compatibility needs to be preserved
// on changes to types. (Use versioned type names to manage
// breaking changes.)
//
// Note: this functionality is not currently available in the official
// protobuf release, and it is not used for type URLs beginning with
// type.googleapis.com.
//
// Schemes other than `http`, `https` (or the empty scheme) might be
// used with implementation specific semantics.
//
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
// Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

在源码中有一个字节类型的数组,很明显,我们将用这个数组来存储我们的代码了,任何类型都可转换成字节,因此可以存储到该字段里

定义服务

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


import "any.proto";

message CallRequest {
string greeting = 1;
map<string, string> infos = 2;
}

message CallResponse {
string reply = 1;
repeated google.protobuf.Any details = 2;
}

message Res {
string reply = 4;
}
service CallService {
rpc SayCall(CallRequest) returns (CallResponse){}
}

启动服务端

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

import (
"context"
"net"

"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
pb "github.com/yuwe1/gopratice/proto-pratice/protodemo/protodemo1"
"google.golang.org/grpc"
)

type CallServer struct {
}

func (c *CallServer) SayCall(ctx context.Context, re *pb.CallRequest) (res *pb.CallResponse, err error) {

var an *any.Any
if re.Infos["A"] == "B" {
an, err = ptypes.MarshalAny(&pb.Res{Reply: "请求正确"})
} else {
an, err = ptypes.MarshalAny(&pb.Res{Reply: "请求出错"})
}
return &pb.CallResponse{
Reply: "Hello World !!",
Details: []*any.Any{an},
}, nil
}

func main() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}

// 新建一个grpc服务器
grpcServer := grpc.NewServer()
// 向grpc服务器注册SayHelloServer
pb.RegisterCallServiceServer(grpcServer, &CallServer{})
// 启动服务
grpcServer.Serve(lis)
}

启动客户端

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

import (
"context"
"log"

"google.golang.org/grpc"

pb "github.com/yuwe1/gopratice/proto-pratice/protodemo/protodemo1"
)

func main() {
// 创建一个 gRPC channel 和服务器交互
conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
if err != nil {
log.Fatalf("Dial failed:%v", err)
}
defer conn.Close()

// 创建客户端
client := pb.NewCallServiceClient(conn)

// 直接调用
resp1, err := client.SayCall(context.Background(), &pb.CallRequest{
Greeting: "Hello Server 1 !!",
Infos: map[string]string{"A": "B"},
})

log.Printf("Resp1:%+v", resp1)

resp2, err := client.SayCall(context.Background(), &pb.CallRequest{
Greeting: "Hello Server 2 !!",
})

log.Printf("Resp2:%+v", resp2)
}

现在自己运行看看会出现什么内容吧

推荐阅读


本文欢迎转载,转载请联系作者,谢谢!


打开微信扫一扫,关注微信公众号