Go中使用 Protobuf 初体验

什么是 Protobuf

Protocol Buffers(简称 Protobuf)是由 Google 开发的一种轻便高效的结构化数据存储格式,用于在不同语言之间序列化和反序列化数据。它被广泛应用于 RPC(远程过程调用)系统和数据存储系统中。

Protobuf 的核心理念是定义 .proto 文件,这个文件描述了数据结构。通过编译 .proto 文件,可以生成不同编程语言的代码,用于读写这些数据结构。

Protobuf 的优势

  • 高效:二进制格式比文本格式(如 JSON、XML)更小、更快。
  • 跨语言:支持多种编程语言,如 C++、Java、Python、Go 等。
  • 兼容性:支持向后兼容,可以在不影响旧代码的情况下添加新的字段。

项目结构和实操例子

以下是一个简单的 Protobuf 和 Go 的实操例子,展示了如何定义、编译和使用 Protobuf。

项目结构

go 复制代码
your_project/
├── proto/
│   └── user.proto
├── server/
│   └── server.go
├── client/
│   └── client.go
└── go.mod

步骤 1: 创建项目结构

sh 复制代码
mkdir -p your_project/{proto,server,client}
cd your_project
go mod init example.com/your_project

步骤 2: 定义 user.proto 文件

your_project/proto 目录中创建 user.proto 文件:

go 复制代码
syntax = "proto3";

package user;

option go_package = "./proto";

service UserService {
    rpc GetUser (GetUserRequest) returns (GetUserResponse);
}

message GetUserRequest {
    int32 id = 1;
}

message GetUserResponse {
    User user = 1;
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

步骤 3: 安装 Protobuf 编译器和插件

确保你已经安装了 Protobuf 编译器 protoc。你可以从 Protobuf Releases 页面下载相应的版本。

然后安装 protoc-gen-goprotoc-gen-go-grpc 插件:

sh 复制代码
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

步骤 4: 编译 Protobuf 文件

your_project 目录下运行以下命令生成 Go 代码:

sh 复制代码
protoc --go_out=. --go-grpc_out=. proto/user.proto

这会在 proto 目录中生成 user.pb.gouser_grpc.pb.go 文件。

步骤 5: 实现服务器

your_project/server 目录中创建 server.go 文件:

go 复制代码
package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "example.com/your_project/proto"
)

type server struct {
    pb.UnimplementedUserServiceServer
}

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    user := &pb.User{
        Id:    req.Id,
        Name:  "John Doe",
        Email: "johndoe@example.com",
    }
    return &pb.GetUserResponse{User: user}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    grpcServer := grpc.NewServer()
    pb.RegisterUserServiceServer(grpcServer, &server{})
    log.Println("Server is running on port 50051")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

步骤 6: 实现客户端

your_project/client 目录中创建 client.go 文件:

go 复制代码
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "example.com/your_project/proto"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    client := pb.NewUserServiceClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    req := &pb.GetUserRequest{Id: 1}
    res, err := client.GetUser(ctx, req)
    if err != nil {
        log.Fatalf("could not get user: %v", err)
    }
    log.Printf("User: %v", res.User)
}

运行步骤

运行服务器

  1. 打开一个终端窗口。
  2. 导航到 your_project/server 目录。
  3. 运行以下命令:
sh 复制代码
go run server.go

你应该会看到服务器正在监听端口 50051。

运行客户端

  1. 打开另一个终端窗口。
  2. 导航到 your_project/client 目录。
  3. 运行以下命令:
sh 复制代码
go run client.go

你应该会看到客户端向服务器发送请求,并接收并打印服务器返回的用户信息。

通过以上步骤,你将了解如何使用 Protobuf 定义数据结构,并通过 gRPC 实现一个简单的客户端-服务器应用程序。

例子 2:序列化

example.com/your_project/proto 新建 example.go:

go 复制代码
syntax = "proto3";

package example;

option go_package = "./proto";

// 定义一个简单的消息类型
message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

example.com/your_project/ 下运行

sh 复制代码
protoc --go_out=. --go-grpc_out=. proto/example.proto

example.com/your_project/ 新建 main.go

go 复制代码
package main

import (
	"fmt"
	"log"

	pb "github.com/chengxuyuanermao/test/proto"
	"google.golang.org/protobuf/proto"
)

func main() {
	// 创建一个新的 Person 消息
	p := &pb.Person{
		Name:  "John Doe",
		Id:    1234,
		Email: "johndoe@example.com",
	}

	// 序列化消息到二进制格式
	data, err := proto.Marshal(p)
	if err != nil {
		log.Fatalf("Marshaling error: %v", err)
	}

	// 打印序列化的二进制数据
	fmt.Printf("Serialized data: %x\n", data)

	// 反序列化二进制数据到消息
	newPerson := &pb.Person{}
	err = proto.Unmarshal(data, newPerson)
	if err != nil {
		log.Fatalf("Unmarshaling error: %v", err)
	}

	// 打印反序列化的消息
	fmt.Printf("Unmarshaled data: %v\n", newPerson)
}

运行 main.go:

sh 复制代码
Serialized data: 0a084a6f686e20446f6510d2091a136a6f686e646f65406578616d706c652e636f6d
Unmarshaled data: name:"John Doe" id:1234 email:"johndoe@example.com"
相关推荐
.生产的驴31 分钟前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲36 分钟前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
hanglove_lucky2 小时前
本地摄像头视频流在html中打开
前端·后端·html
探索云原生3 小时前
在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析
ai·云原生·kubernetes·go·gpu
皓木.3 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com3 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟3 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行4 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、4 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
coding侠客4 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘
java·spring boot·后端