grpc
Protocol Buffers
Protocol Buffers 最初是在 Google 开发的,用于处理索引服务器请求/响应协议。是一种灵活、高效、自动化的解决方案,可以准确地解决这个问题。使用协议缓冲区,您可以编写.proto要存储的数据结构的描述。由此,协议缓冲区编译器创建了一个类,该类以高效的二进制格式实现协议缓冲区数据的自动编码和解析。生成的类为构成协议缓冲区的字段提供了 getter 和 setter,并将读写协议缓冲区的细节作为一个单元处理。重要的是,协议缓冲区格式支持随着时间的推移扩展格式的想法,这样代码仍然可以读取使用旧格式编码的数据。
随着系统的发展,它获得了许多其他功能和用途:
- 自动生成的序列化和反序列化代码避免了手动解析的需要。
- 除了用于短期 RPC(远程过程调用)请求之外,人们还开始使用 protocol buffers 作为一种方便的自描述格式,用于持久存储数据(例如在 Bigtable 中)。
- 服务器 RPC 接口开始被声明为协议文件的一部分,protocol 编译器生成存根类,用户可以使用服务器接口的实际实现来覆盖这些类。
类比
个人认为如果要将 ProtoBuf、XML、JSON 三者放到一起去比较,应该区分两个维度。一个是数据结构化,一个是数据序列化。这里的数据结构化主要面向开发或业务层面,数据序列化面向通信或存储层面,当然数据序列化也需要“结构”和“格式”,所以这两者之间的区别主要在于面向领域和场景不同,一般要求和侧重点也会有所不同。数据结构化侧重人类可读性甚至有时会强调语义表达能力,而数据序列化侧重效率和压缩。
JSON、XML更适合数据结构化而非数据序列化
- XML、JSON、ProtoBuf 都具有数据结构化和数据序列化的能力
- XML、JSON 更注重数据结构化,关注人类可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足(为保证极致的效率,会舍弃一部分元信息)
- ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。
.proto文件语法
官网:https://developers.google.com/protocol-buffers/docs/proto3
消息
syntax = "proto3";
message SearchRequest {
optional string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- optional: 该字段可能会或可能不会设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像我们type在示例中为电话号码所做的那样。否则,使用系统默认值:数字类型为零,字符串为空字符串,布尔值为 false。对于嵌入的消息,默认值始终是消息的“默认实例”或“原型”,它没有设置任何字段。调用访问器以获取尚未显式设置的可选(或必需)字段的值始终返回该字段的默认值。
- repeated:该字段可以重复任意次数(包括零次)。重复值的顺序将保留在协议缓冲区中。将重复字段视为动态大小的数组。
- required:必须提供该字段的值,否则消息将被视为“未初始化”。尝试构建未初始化的消息将抛出一个RuntimeException. 解析未初始化的消息将抛出一个IOException. 除此之外,必填字段的行为与可选字段完全相同。
proto3 已舍弃 required 字段,optional 字段也无法显示使用(因为缺省默认就设置为 optional)
添加注释
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
默认值
解析消息时,如果编码的消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于 bool,默认值为 false。
- 对于数字类型,默认值为零。
- 对于enums,默认值是第一个定义的 enum value,它必须是 0。
枚举
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;
}
每个枚举定义都必须包含一个映射到零的常量作为其第一个元素
导入
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
请注意,此功能在 Java 中不可用
嵌套
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
任何
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
map
map<string, Project> projects = 3;
服务
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
metadata
元数据,即数据的数据,标识数据的数据,类似http的请求头
1)定义proto
syntax = "proto3";
option go_package="./;proto";
service Greeter{
rpc GetString(ReqData)returns(ResData);
}
message ReqData{
string data = 1;
}
message ResData{
string data = 1;
}
2)定义服务端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"gotest/metadata/proto"
"net"
)
type server struct {
}
func (s *server) GetString(ctx context.Context,req *proto.ReqData) (*proto.ResData, error){
//从上下文中读取出键值对
md, ok := metadata.FromIncomingContext(ctx)
if ok{
fmt.Println(md)
}
fmt.Println(req.GetData())
return &proto.ResData{
Data: "Data from server",
},nil
}
func main(){
lis,err := net.Listen("tcp",":5000")
if err != nil{
fmt.Println(err)
}
s := grpc.NewServer()
proto.RegisterGreeterServer(s,&server{})
s.Serve(lis)
}
3)定义客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"gotest/metadata/proto"
)
func main(){
conn,err := grpc.Dial("127.0.0.1:5000",grpc.WithInsecure())
if err != nil{
fmt.Println(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
//构建键值对
md := metadata.Pairs(
"key1", "val1-2",
"key2", "val2",
)
// 将键值对构建metadata
ctx := metadata.NewOutgoingContext(context.Background(), md)
r,err := c.GetString(ctx,&proto.ReqData{
Data: "Data from client",
})
if err != nil{
fmt.Println(err)
}
fmt.Println(r.GetData())
}
拦截器
1)定义proto
syntax = "proto3";
option go_package="./;proto";
service Greeter{
rpc GetString(ReqData)returns(ResData);
}
message ReqData{
string data = 1;
}
message ResData{
string data = 1;
}
2)服务端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"gotest/metadata/proto"
"net"
)
type server struct {
}
func (s *server) GetString(ctx context.Context,req *proto.ReqData) (*proto.ResData, error){
fmt.Println(req.GetData())
return &proto.ResData{
Data: "Data from server",
},nil
}
func main(){
lis,err := net.Listen("tcp",":5000")
if err != nil{
fmt.Println(err)
}
// 注册interceptor
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("收到一个请求")
if err != nil {
return
}
// 继续处理请求
return handler(ctx, req)
}
opts := grpc.UnaryInterceptor(interceptor)
s := grpc.NewServer(opts)
proto.RegisterGreeterServer(s,&server{})
s.Serve(lis)
}
3)客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"gotest/metadata/proto"
"log"
"time"
)
func Clientinterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
log.Printf("method == %s ; req == %v ; rep == %v ; duration == %s ; error == %v\n", method, req, reply, time.Since(start), err)
return err
}
func main(){
opt := grpc.WithUnaryInterceptor(Clientinterceptor)
//注册interceptor
conn,err := grpc.Dial("127.0.0.1:5000",grpc.WithInsecure(),opt)
if err != nil{
fmt.Println(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r,err := c.GetString(context.Background(),&proto.ReqData{
Data: "Data from client",
})
if err != nil{
fmt.Println(err)
}
fmt.Println(r.GetData())
}还没有评论,来说两句吧
