pb文件细节

351 阅读9分钟

理解 Protocol Buffers(protobuf)消息

示例 .proto 文件

首先,定义一个简单的消息类型。

syntax = "proto3";

package example;

// 定义一个 Person 消息类型
message Person {
    int32 id = 1;          // 唯一ID
    string name = 2;       // 姓名
    string email = 3;      // 邮箱地址
}

生成 Go 代码

使用 protoc 命令生成 Go 代码:

protoc --go_out=. --go_opt=paths=source_relative example.proto

这会生成一个 example.pb.go 文件,包含 Person 消息的 Go 语言实现。

使用生成的代码

以下是如何在 Go 中使用生成的代码。

示例:创建和操作消息对象

  1. 导入生成的包:首先,在你的 Go 文件中导入生成的包。
package main

import (
    "fmt"
    "log"
    "github.com/user/project/example" // 假设模块名为 github.com/user/project
    "google.golang.org/protobuf/proto"
)
  1. 创建消息对象:使用生成的类型创建和操作消息对象。
func main() {
    // 创建一个 Person 消息对象
    p := &example.Person{
        Id:    1234,
        Name:  "John Doe",
        Email: "johndoe@example.com",
    }

    // 序列化消息对象为二进制格式
    data, err := proto.Marshal(p)
    if err != nil {
        log.Fatalf("Failed to marshal: %v", err)
    }

    fmt.Printf("Serialized data: %x\n", data)

    // 反序列化二进制数据回消息对象
    newPerson := &example.Person{}
    err = proto.Unmarshal(data, newPerson)
    if err != nil {
        log.Fatalf("Failed to unmarshal: %v", err)
    }

    fmt.Printf("Deserialized Person: %+v\n", newPerson)
}

深入理解消息

  1. 字段编号:每个字段都有一个唯一的编号(如 id = 1name = 2),用于在序列化和反序列化时识别字段。这些编号在 .proto 文件中定义。

  2. 类型:protobuf 支持多种数据类型,包括基本类型(如 int32string)和复杂类型(如嵌套消息、枚举)。这些类型在序列化时被编码为高效的二进制格式。

  3. 序列化与反序列化:消息对象可以序列化为二进制数据(高效传输和存储),也可以从二进制数据反序列化回消息对象。

示例解释

  1. 创建消息对象:通过生成的 Person 类型创建一个消息对象,并赋值给其字段。
p := &example.Person{
    Id:    1234,
    Name:  "John Doe",
    Email: "johndoe@example.com",
}
  1. 序列化消息对象:使用 proto.Marshal 方法将消息对象序列化为二进制数据。
data, err := proto.Marshal(p)
if err != nil {
    log.Fatalf("Failed to marshal: %v", err)
}
  1. 输出序列化数据:打印序列化后的二进制数据。
fmt.Printf("Serialized data: %x\n", data)
  1. 反序列化消息对象:使用 proto.Unmarshal 方法将二进制数据反序列化回消息对象。
newPerson := &example.Person{}
err = proto.Unmarshal(data, newPerson)
if err != nil {
    log.Fatalf("Failed to unmarshal: %v", err)
}
  1. 输出反序列化后的消息对象:打印反序列化后的消息对象。
fmt.Printf("Deserialized Person: %+v\n", newPerson)

总结

通过以上示例,可以理解一个 protobuf 消息的定义、生成和使用过程。protobuf 消息的核心是高效的序列化和反序列化机制,通过字段编号和类型定义,可以在不同编程语言之间高效地传输和处理结构化数据。

Protocol Buffers(protobuf)

是一种用于序列化结构化数据的工具。它定义了一个独立于编程语言和平台的格式,常用于数据存储和 RPC 通信。下面介绍如何编写 .proto 文件、如何定义消息和服务、以及数据类型的对应关系和对象定义。

1. 编写 .proto 文件

.proto 文件是 Protocol Buffers 的核心,用于定义消息类型和服务。

基本结构

syntax = "proto3";

package example;

message MessageName {
    // 字段定义
}
  • syntax 指定使用的 protobuf 语法版本,这里使用 proto3
  • package 指定消息所属的包,用于避免命名冲突。
  • message 定义一个消息类型。

2. 定义消息类型

消息类型类似于结构体或类,用于描述数据结构。每个字段都有一个类型、名字和编号。

基本类型对应关系

Protobuf 类型Go 类型Java 类型
doublefloat64double
floatfloat32float
int32int32int
int64int64long
uint32uint32int
uint64uint64long
sint32int32int
sint64int64long
fixed32uint32int
fixed64uint64long
sfixed32int32int
sfixed64int64long
boolboolboolean
stringstringString
bytes[]bytebyte[]

示例消息类型定义

syntax = "proto3";

package example;

// 定义一个简单的消息类型
message Person {
    int32 id = 1;           // 唯一ID
    string name = 2;        // 姓名
    string email = 3;       // 邮箱地址
}

// 嵌套消息类型
message AddressBook {
    repeated Person people = 1;  // 多个 Person 对象
}
  • int32 id = 1;:字段类型为 int32,字段名为 id,字段编号为 1
  • repeated Person people = 1;repeated 表示一个字段可以包含多个值(类似于数组或列表)。

3. 定义服务

服务定义了可以通过 RPC 调用的方法。

示例服务定义

syntax = "proto3";

package example;

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloResponse);
}

4. 生成 Go 代码

使用 protoc 命令生成 Go 代码:

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       example.proto

5. 使用生成的代码

生成的 Go 代码包含消息和服务的定义,可以直接在 Go 项目中使用。

示例:实现 gRPC 服务器

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "path/to/your/generated/code"

    "google.golang.org/grpc/reflection"
)

const (
    port = ":50051"
)

type server struct {
    pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    return &pb.HelloResponse{Message: "Hello " + in.Name}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})

    // Register reflection service on gRPC server.
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

总结

  • 编写 .proto 文件:定义消息类型和服务。
  • 基本类型对应关系:了解 protobuf 类型和对应的 Go、Java 类型。
  • 生成 Go 代码:使用 protoc 编译器生成 .pb.go 文件。
  • 使用生成的代码:在 Go 项目中实现业务逻辑和 gRPC 服务器。

通过以上步骤,可以在 Go 项目中成功使用 Protocol Buffers 定义和使用数据类型及服务。

pb消息数据结构定义细节

下面是一个表格,用于列举 Protocol Buffers(protobuf)中基本数据类型、数组和集合容器的定义方式,并将其与 Java 和 Go 进行对比。

基本数据类型

数据类型Protobuf 定义Java 类型Go 类型
整数(32位)int32intint32
整数(64位)int64longint64
无符号整数(32位)uint32intuint32
无符号整数(64位)uint64longuint64
带符号整数(32位)sint32intint32
带符号整数(64位)sint64longint64
定长整数(32位)fixed32intuint32
定长整数(64位)fixed64longuint64
定长带符号整数(32位)sfixed32intint32
定长带符号整数(64位)sfixed64longint64
布尔类型boolbooleanbool
字符串stringStringstring
字节数组bytesbyte[][]byte
浮点数(32位)floatfloatfloat32
浮点数(64位)doubledoublefloat64

数组和集合容器

Protobuf 不直接支持集合类型(如 SetMap),但可以通过消息类型和重复字段来模拟。

数组(Repeated Field)

类型Protobuf 定义Java 类型Go 类型
整数数组repeated int32 ids = 1;List<Integer>[]int32
字符串数组repeated string names = 2;List<String>[]string
自定义类型数组repeated Person people = 3;List<Person>[]Person

嵌套消息类型(可以模拟 Map 和 Set)

类型Protobuf 定义Java 类型Go 类型
嵌套消息类型message Person { int32 id = 1; string name = 2; }class Person { int id; String name; }type Person struct { Id int32; Name string; }

模拟 Map

Protobuf 支持映射类型,但它们必须在 Protobuf 3.0 以上版本中定义为消息类型。

类型Protobuf 定义Java 类型Go 类型
Map 类型map<string, int32> scores = 1;Map<String, Integer>map[string]int32

Protobuf 定义示例

示例 .proto 文件

syntax = "proto3";

package example;

// 定义一个 Person 消息类型
message Person {
    int32 id = 1;          // 唯一ID
    string name = 2;       // 姓名
    string email = 3;      // 邮箱地址
}

// 定义一个 AddressBook 消息类型,包含一个 Person 的 repeated 字段
message AddressBook {
    repeated Person people = 1;  // 多个 Person 对象
}

// 定义一个包含 Map 类型的消息
message Scores {
    map<string, int32> scores = 1;
}

总结

上面的表格和示例展示了如何在 Protobuf 中定义基本数据类型、数组和集合容器,并与 Java 和 Go 类型进行对比。通过这些定义,可以在不同的编程语言之间高效地传输和处理结构化数据。

定义Req 和 Rep

在 Protocol Buffers(protobuf)中,定义请求和响应消息主要用于 RPC(远程过程调用)服务。下面是详细的步骤和示例,展示如何定义请求和响应消息,并将其用于 gRPC 服务。

示例 .proto 文件

假设我们要定义一个简单的 gRPC 服务,该服务有一个方法 SayHello,接受一个 HelloRequest 并返回一个 HelloResponse

定义请求和响应消息

syntax = "proto3";

package example;

// 请求消息
message HelloRequest {
    string name = 1;  // 请求中包含一个名字字段
}

// 响应消息
message HelloResponse {
    string message = 1;  // 响应中包含一个消息字段
}

定义服务

syntax = "proto3";

package example;

import "google/protobuf/empty.proto";

// 请求消息
message HelloRequest {
    string name = 1;  // 请求中包含一个名字字段
}

// 响应消息
message HelloResponse {
    string message = 1;  // 响应中包含一个消息字段
}

// 定义 gRPC 服务
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloResponse);
    rpc SayGoodbye (google.protobuf.Empty) returns (HelloResponse);
}

生成 Go 代码

使用 protoc 命令生成 Go 代码:

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       example.proto

这会生成包含消息和服务定义的 Go 代码文件 example.pb.goexample_grpc.pb.go

使用生成的代码

下面是一个完整的示例,展示如何在 Go 中使用生成的代码来实现 gRPC 服务器和客户端。

实现 gRPC 服务器

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "path/to/your/generated/code"

    "google.golang.org/grpc/reflection"
)

// 定义服务器结构体
type server struct {
    pb.UnimplementedGreeterServer
}

// 实现 SayHello 方法
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    return &pb.HelloResponse{Message: "Hello " + in.Name}, nil
}

// 实现 SayGoodbye 方法
func (s *server) SayGoodbye(ctx context.Context, in *pb.Empty) (*pb.HelloResponse, error) {
    return &pb.HelloResponse{Message: "Goodbye!"}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})

    // Register reflection service on gRPC server.
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

实现 gRPC 客户端

package main

import (
    "context"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"
    pb "path/to/your/generated/code"
    "google.golang.org/protobuf/types/known/emptypb"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // 连接到服务器
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // 联系服务器并打印回复
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())

    // 请求 SayGoodbye 方法
    r2, err := c.SayGoodbye(ctx, &emptypb.Empty{})
    if err != nil {
        log.Fatalf("could not say goodbye: %v", err)
    }
    log.Printf("Goodbye Message: %s", r2.GetMessage())
}

解释

  • 定义请求和响应消息:在 .proto 文件中定义 HelloRequestHelloResponse 消息类型,分别用于请求和响应。
  • 定义服务:在 .proto 文件中定义 Greeter 服务,包含 SayHelloSayGoodbye 方法,分别接收 HelloRequestgoogle.protobuf.Empty 请求,并返回 HelloResponse
  • 生成代码:使用 protoc 命令生成 Go 代码文件,包含消息和服务的定义。
  • 实现服务器:实现 Greeter 服务的服务器端逻辑,包括 SayHelloSayGoodbye 方法。
  • 实现客户端:实现 gRPC 客户端,连接到服务器并调用 SayHelloSayGoodbye 方法。

通过上述步骤,可以使用 Protocol Buffers 和 gRPC 定义和实现请求和响应消息,并在 Go 中使用这些消息进行 RPC 调用。