理解 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 中使用生成的代码。
示例:创建和操作消息对象
- 导入生成的包:首先,在你的 Go 文件中导入生成的包。
package main
import (
"fmt"
"log"
"github.com/user/project/example" // 假设模块名为 github.com/user/project
"google.golang.org/protobuf/proto"
)
- 创建消息对象:使用生成的类型创建和操作消息对象。
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)
}
深入理解消息
-
字段编号:每个字段都有一个唯一的编号(如
id = 1、name = 2),用于在序列化和反序列化时识别字段。这些编号在.proto文件中定义。 -
类型:protobuf 支持多种数据类型,包括基本类型(如
int32、string)和复杂类型(如嵌套消息、枚举)。这些类型在序列化时被编码为高效的二进制格式。 -
序列化与反序列化:消息对象可以序列化为二进制数据(高效传输和存储),也可以从二进制数据反序列化回消息对象。
示例解释
- 创建消息对象:通过生成的
Person类型创建一个消息对象,并赋值给其字段。
p := &example.Person{
Id: 1234,
Name: "John Doe",
Email: "johndoe@example.com",
}
- 序列化消息对象:使用
proto.Marshal方法将消息对象序列化为二进制数据。
data, err := proto.Marshal(p)
if err != nil {
log.Fatalf("Failed to marshal: %v", err)
}
- 输出序列化数据:打印序列化后的二进制数据。
fmt.Printf("Serialized data: %x\n", data)
- 反序列化消息对象:使用
proto.Unmarshal方法将二进制数据反序列化回消息对象。
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)
总结
通过以上示例,可以理解一个 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 类型 |
|---|---|---|
double | float64 | double |
float | float32 | float |
int32 | int32 | int |
int64 | int64 | long |
uint32 | uint32 | int |
uint64 | uint64 | long |
sint32 | int32 | int |
sint64 | int64 | long |
fixed32 | uint32 | int |
fixed64 | uint64 | long |
sfixed32 | int32 | int |
sfixed64 | int64 | long |
bool | bool | boolean |
string | string | String |
bytes | []byte | byte[] |
示例消息类型定义
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位) | int32 | int | int32 |
| 整数(64位) | int64 | long | int64 |
| 无符号整数(32位) | uint32 | int | uint32 |
| 无符号整数(64位) | uint64 | long | uint64 |
| 带符号整数(32位) | sint32 | int | int32 |
| 带符号整数(64位) | sint64 | long | int64 |
| 定长整数(32位) | fixed32 | int | uint32 |
| 定长整数(64位) | fixed64 | long | uint64 |
| 定长带符号整数(32位) | sfixed32 | int | int32 |
| 定长带符号整数(64位) | sfixed64 | long | int64 |
| 布尔类型 | bool | boolean | bool |
| 字符串 | string | String | string |
| 字节数组 | bytes | byte[] | []byte |
| 浮点数(32位) | float | float | float32 |
| 浮点数(64位) | double | double | float64 |
数组和集合容器
Protobuf 不直接支持集合类型(如 Set、Map),但可以通过消息类型和重复字段来模拟。
数组(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.go 和 example_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文件中定义HelloRequest和HelloResponse消息类型,分别用于请求和响应。 - 定义服务:在
.proto文件中定义Greeter服务,包含SayHello和SayGoodbye方法,分别接收HelloRequest和google.protobuf.Empty请求,并返回HelloResponse。 - 生成代码:使用
protoc命令生成 Go 代码文件,包含消息和服务的定义。 - 实现服务器:实现
Greeter服务的服务器端逻辑,包括SayHello和SayGoodbye方法。 - 实现客户端:实现 gRPC 客户端,连接到服务器并调用
SayHello和SayGoodbye方法。
通过上述步骤,可以使用 Protocol Buffers 和 gRPC 定义和实现请求和响应消息,并在 Go 中使用这些消息进行 RPC 调用。