protocbuf

protobuf是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是 二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据 优势:

  1. 序列化后体积相比Json和XML很小,适合网络传输

  2. 支持跨平台多语言

  3. 消息格式升级和兼容性还不错

  4. 序列化反序列化速度很快

 

安装protocbuf编译器:

https://github.com/protocolbuffers/protobuf/releases

 

 

安装protocbuf go

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

安装完成后会在GOPATH中的bin目录下生成一个protoc-gen-go.exe的文件

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

 

安装gRpc

 

使用proto

// 指定当前proto语法的版本,有2和3
syntax = "proto3";
​
//option go_package = "path;name"; path 表示生成的go文件的存放地址,会自动生成目录
//name 表示生成的go文件所属的包名
option go_package = "../service";
// 指定等会儿文件生成出来的pakage
package service;
​
// 消息 传输的对象
message Student {
  string name = 1;
  bool male = 2;
  repeated int32 scores = 3;
}

 

使用命令

 protoc --go_out=./ .\user.proto

会在proto文件的上层目录生成一个service的文件夹,在该文件夹中存放着生成的go文件

 

如何在程序中使用

创建一个main.go

package main
​
import (
    "fmt"
    "google.golang.org/protobuf/proto"
    "pbdemo/service"
)
​
func main() {
    user := &service.Student{
        Name: "张三",
        Age:  20,
    }
    // 序列化protoc
    marshal, err := proto.Marshal(user)
    if err != nil {
        panic(err)
    }
​
    // 反序列化
    newUser := &service.Student{}
    err = proto.Unmarshal(marshal, newUser)
    if err != nil {
        panic(err)
    }
​
    fmt.Println(newUser.String())
}

输出

name:"张三" age:20

 

proto文件介绍

 

message介绍

 

message:protobuf中定义一个消息类型是通过关键字message字段指定的

消息就是需要传输的数据格式的定义。 message关键字类似于C++中的class,Java中的class,go中的struct 例如:

message Student {
  string name = 1;
  int32 age = 2;
}

在消息中承载的数据分别对应于每一个字段 其中每个字段都有一个名字和一种类型

 

字段规则

required :消息体中必填字段,不设置会导致编解码异常。在proto2中是需要填写的,在3中是默认就有的 optional: 消息体中可选字段。 repeated:消息体中可重复字段,重复的值的顺序会被保留在o中重复的会被定义为切

 

message Student {
  string name = 1;
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4;
}

 

    Name     string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age      int32    `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    Password *string  `protobuf:"bytes,3,opt,name=password,proto3,oneof" json:"password,omitempty"`
    Address  []string `protobuf:"bytes,4,rep,name=address,proto3" json:"address,omitempty"`

可以看到Name和Password是有区别的,一个是string一个是string指针。而Address则是一个string切片

 

字段映射

 

Protobuf Type说明C++ TypeJava TypePython Type[2]Go Type
float 固定4个字节 float float float float32
double 固定8个字节 double double float float64
int32 varint编码 int32 int int int32
uint32 varint编码 uint32 int int/long uint32
uint64 varint编码 uint64 long int/long uint64
sint32 zigzag 和 varint编码 int32 int int int32
sint64 zigzag 和 varint编码 int64 long int/long int64
fixed32 固定4个字节 uint32 int int uint32
fixed64 固定8个字节 uint64 long int/long uint64
sfixed32 固定4个字节 int32 int int int32
sfixed64 固定8个字节 int64 long int/long int64
bool 固定一个字节 bool boolean bool bool
string Lenth-Delimited uint64 String str/unicode string
bytes Lenth-Delimited string ByteString str []byte
bytes Lenth-Delimited string ByteString str []byte

 

默认值

 

对于strings, 默认值是空字符串(注, 是"", 而不是null)

对于bytes, 默认值是空字节(注, 应该是byte[0], 注意这里也不是null)

对于bool, 默认值是false.

对于数字类型, 默认值是0.

对于枚举, 默认值是第一个定义的枚举值, 而这个值必须是0.

对于消息字段, 默认值和语言相关,参考generated code guide

对于重复字段, 默认值是空(通常都是空列表)

 

标识号

标识号:在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0,2^29-1]范围内的一个 整数。

message Student {
  string name = 1; (位置1)
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4; (位置4)
}

 

定义多个消息类型

一个protoc文件可以定义多个消息类型

 

message UserRequest {
  string name = 1; (位置1)
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4; (位置4)
}

message UserResponse {
  string name = 1; (位置1)
  int32 age = 2;
  optional string password = 3;
  repeated string address = 4; (位置4)
}

 

嵌套消息

可以在其他消息类型中定义、使用消息类型,在下面的例子中,Person消息就定义在Personlnfo消息内 如:

message PersonInfo {
	message Person{
		string name = 1; (位置1)
  		int32 age = 2;
	}
	repeated Person info = 1; 
}

 

如果你想在它的父消息类型的外部重用这个消息类型,你需要以Personlnfo.Person的形式使用它,如:

 

message PersonMessage{
	PersonInfo.Person info = 1;
}

 

定义服务

 

如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根

 

service SearchService{
	//rpc 服务的函数名 (传入参数) 返回 (返回参数)
	rpc Search (SearchRequest) returns (SearchResponse);
}

 

上述代码表示,定义了一个RPC服务,该方法接收SearchRequest返回SearchResponse

 

gRpc实例

 

Rpc和gRpc

RPC (Remote Procedure Call) 远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。 在OSI网络七层模型中(应,表,会,传,网,数,物),RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程 序更加容易 过程是什么?过程就是业务处理、计算任务,更直白的说,就是程序,就是像调用本地方法一样调用远程 的过程

RPC采用客户端/服务端的模式,通过request-response消息模式实现

image-20230908195808018

 

gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 RPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

 

image-20230908200102770

 

底层协议:

HTTP/2

GRPC-WEB

 

HTTP/2

image-20230908200609378

 

  • HTTP/1里的header对应HTTP/2里的 HEADERS frame

  • HTTP/1里的payload对应HTTP/2里的 DATA frame

gGRPC把元数据放到HTTP/2 Headers里,请求参数序列化之后放到 DATA frame里

 

基于HTTP/2的优点

 

  1. 公开标准

  2. HTTP/2的前身是Google的SPDY,有经过实践检验

  3. HTTP/2 天然支持物联网、手机、浏览器

  4. 基于HTTP/2 多语言客户端实现容易

    1. 每个流行的编程语言都会有成熟的HTTP/2 Client

    2. HTTP/2 Client是经过充分测试,可靠的

    3. 用Client发送HTTP/2请求的难度远低于用socket发送数据包/解析数据包

  5. HTTP/2支持Stream和流控

  6. 基于HTTP/2 在Gateway/Proxy很容易支持

  7. HTTP/2 安全性有保证

    1. HTTP/2 天然支持SSL,当然gRPC可以跑在clear text协议 (即不加密) 上

    2. 很多私有协议的rpc可能自己包装了一层TLS支持,使用起来也非常复杂。开发者是否有足够的安全知识? 使用者是否配置对了? 运维者是否能正确理解?

    3. HTTP/2 在公有网络上的传输上有保证。比如这个 CRIME攻击,私有协议很难保证没有这样子的漏洞。

  8. HTTP/2 鉴权成熟

    1. 从HTTP/1发展起来的鉴权系统已经很成熟了,可以无缝用在HTTP/2上

    2. 可以从前端到后端完全打通的鉴权,不需要做任何转换适配

 

实例

 

服务端

 

// 指定当前proto语法的版本,有2和3
syntax = "proto3";

//option go_package = "path;name"; path 表示生成的go文件的存放地址,会自动生成目录
//name 表示生成的go文件所属的包名
option go_package = "../service";
// 指定等会儿文件生成出来的pakage
package service;

message ProductRequest{
  int32 prod_id = 1;
}

message ProductResponse{
  int32 prod_stock = 1;
}

service ProdService {
  // 定义方法
  rpc GetProductStock(ProductRequest) returns(ProductResponse);
}

 

生成:

protoc --go_out=./ --go-grpc_out=./ .\product.proto

 

grpc_service.go

package main

import (
	"demo1/service"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	rpcServer := grpc.NewServer()

	service.RegisterProdServiceServer(rpcServer, service.ProductService)

	// 启动服务
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatal(" 启动监听出错", err)
	}
	err = rpcServer.Serve(listen)
	if err != nil {
		log.Fatal("启动服务出错", err)
	}

	fmt.Println("启动gRpc成功")
}

 

客户端

 

新建client目录将上面生成的go文件全部copy到该目录下的service目录中

package main
​
import (
    "context"
    "demo1/client/service"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "log"
)
​
func main() {
    // 新建连接。端口8080
    // 没有证书会报错
    conn, err := grpc.Dial(":8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal("服务端连接出错:", err)
    }
    defer conn.Close()
​
    // 调用product_grpc.pb.go中的NewProdServiceClient方法
    prodServiceClient := service.NewProdServiceClient(conn)
​
    //调用
    req := &service.ProductRequest{
        ProdId: 123,
    }
    stock, err := prodServiceClient.GetProductStock(context.Background(), req)
    if err != nil {
        log.Fatal("查询库存出错:", err)
    }
​
    fmt.Println("查询成功:", stock.ProdStock)
}
​
posted @ 2023-09-25 17:10  CrryG_GPC  阅读(36)  评论(0)    收藏  举报