说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性。但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择。而今天总结的Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。本文主要以Golang语言进行介绍。
数据通信、grpc通信、微服务。
首先需要在下载protoc编译器【会很慢,建议使用V**】
将下载好的编译器移动到$GOPATH/bin目录
使用命令安装插件
go get -u github.com/golang/protobuf/protoc-gen-go
我们创建一个p.proto
文件这个例子中message代表一个消息类型,在消息类型中有三个字段,这里不在多说,大家都明白。
syntax = "proto3";
message RequestParm {
string query = 1;
int32 pages = 2;
int32 article_page = 3;
}
现在我们运行一下,目录切换到这个文件的目录执行一下代码
protoc -I. --go_out=plugins=grpc:. p.proto
可以看到编译后出现了p.pb.go
的文件,打开这个文件可以看到,有下面部分代码,我们的消息类型变成了一个结构体
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 |
message ErrorStatus {
string message = 1;
repeated string details = 2;
}
message Bar {
string a = 15;
repeated RequestParm r = 4;
enum Data {
FIRST = 0;
SECOND = 1;
THIRD = 2;
}
Data data = 5;
}
编译之后的部分结果
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:"-"`
}
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
编译之后的结果
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:"-"`
}
message SubMessage {
string sub = 1;
map<string, string> m = 4;
}
编译之后的结果
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:"-"`
}
需要导入import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
编译之后的结果
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的源码
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:"-"`
}
在源码中有一个字节类型的数组,很明显,我们将用这个数组来存储我们的代码了,任何类型都可转换成字节,因此可以存储到该字段里
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){}
}
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)
}
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)
}
现在自己运行看看会出现什么内容吧