Protobuf 入门学习 | 青训营笔记

150 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天

前言

Protobuf(Protocol Buffers 的缩写)是一种由 Google 开发的数据序列化格式。它旨在成为一种语言和平台中立的方式,用于在不同系统、应用程序或进程之间进行结构化数据通信。

Protobuf 定义了一种语言无关的接口定义语言(IDL),可用于描述数据结构的结构。IDL 定义了数据结构的字段及其类型,以及任何其他元数据,例如字段名称、默认值和消息 ID。然后将 IDL 编译为不同编程语言的代码,例如 C++、Java、Python 等。

编码后的数据通常比其他序列化格式(例如 JSON 或 XML)更小、更快速处理。它还提供了不同版本的同一消息之间的向后兼容性,允许新客户端与旧服务器进行通信,反之亦然。

语法

Protobuf 的语法由.proto 文件定义。以下是.proto 文件中使用的一些关键语法元素的详细说明:

1. 语法版本

.proto 文件中应该始终指定使用的语法版本。目前,最新的语法版本是 proto3。例如:

syntax = "proto3";

2. 消息定义

消息是.proto 文件中最重要的元素之一,用于描述一种特定类型的数据结构。消息定义由关键字 message 开始,后跟消息名称和一对花括号,其中包含消息的字段定义。例如:

message Person {
  string name = 1;
  int32 age = 2;
  repeated string phone_number = 3;
}

在上面的示例中,定义了一个名为 Person 的消息类型,它包含三个字段:name、age 和 phone_number。每个字段都有一个类型和一个唯一的数字标识符,用于在序列化和反序列化过程中识别该字段。

3. 字段定义

字段定义描述了消息中包含的特定字段的名称、类型和唯一标识符。字段定义由字段类型、字段名称和字段标识符组成。例如:

string name = 1;

在上面的示例中,定义了一个名为 name 的字符串类型的字段,并使用 1 作为其唯一标识符。标识符应从 1 开始,以便为每个字段分配唯一的标识符。

4. 枚举定义

枚举是一种用于定义一组可能的值的消息类型。枚举定义由关键字 enum 开始,后跟枚举名称和一对花括号,其中包含每个枚举值的名称和唯一的数值。例如:

enum PhoneType {
  MOBILE = 0;
  HOME = 1;
  WORK = 2;
}

在上面的示例中,定义了一个名为 PhoneType 的枚举类型,它包含三个枚举值:MOBILE、HOME 和 WORK。每个枚举值都有一个名称和一个唯一的数值,用于标识该值。

5. 服务定义

服务定义用于定义一组相关的 RPC 方法。服务定义由关键字 service 开始,后跟服务名称和一对花括号,其中包含每个 RPC 方法的定义。例如:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

在上面的示例中,定义了一个名为 SearchService 的服务类型,它包含一个名为 Search 的 RPC 方法。该方法接受一个 SearchRequest 消息作为输入,并返回一个 SearchResponse 消息作为输出。

这些是.proto 文件中最重要的语法元素之一,但还有其他一些元素,如选项、扩展、注释等。了解这些元素可以帮助您更好地理解和使用 Protobuf。

Kitex 生成代码

下面用 Kitex 生成代码进行简单的实践。

首先,idl 文件夹下定义了一个 user.proto 文件,详细内容如下:

syntax = "proto3";  
package user;  
option go_package = "user";  
  
message BaseResp {  
  int32 status_code = 1;//状态码  
  string status_message = 2;//状态描述  
  int64 service_time = 3;//服务时间  
}  
  
message User {  
  int64 id = 1;//用户id  
  string name = 2;//用户名称  
  int64 follow_count = 3;//关注总数  
  int64 follower_count = 4;//粉丝总数  
  bool is_follow = 5;//true-已关注,false-未关注  
  string avatar = 6; // 用户头像  
  string background_image = 7; // 用户个人页顶部大图  
  string signature = 8; // 个人简介  
  int64 total_favorited = 9; // 获赞数量  
  int64 work_count = 10; // 作品数量  
  int64 favorite_count = 11; // 点赞数量  
}  
  
message check_user_request {  
  string username = 1; //登录用户名  
  string password = 2;//登录密码  
}  
  
message check_user_response {  
  BaseResp base_resp = 1;  
  int64 user_id = 2;//用户id  
}  
  
message register_user_request {  
  string username = 1;//注册用户名,最长32个字符  
  string password = 2;//密码,最长32个字符  
}  
  
message register_user_response {  
  BaseResp base_resp = 1;  
  int64 user_id = 2;//用户id  
  string token = 3;//用户鉴权token  
}  
  
  
message user_info_request {  
  int64 user_id = 1;//用户id  
  string token = 2;//用户鉴权token  
}  
  
message user_info_response {  
  BaseResp base_resp = 1;  
  User user = 2;//用户信息  
}  
  
service UserService {  
  rpc CheckUser (check_user_request) returns (check_user_response) {}  
  rpc RegisterUser (register_user_request) returns (register_user_response) {}  
  rpc UserInfo (user_info_request) returns (user_info_response) {}  
}

接着用下面的命令生成代码:

$ kitex -module ephmeral.com/Kitex_example -service user idl/user.proto
  • -module 参数是 go mod 里面的地址,因为我是放在 GOPATH 外
  • -service 参数表示服务的名称

然后看下生成代码的结构:

Pasted image 20230220173423.png

  • kitex_gen:里面自动生成好的代码,可以不用管
  • handler.go:这个文件是 rpc 调用处理函数,里面可以看到在 proto 中定义好的 service,需要我们去具体实现的逻辑
  • main.go:主函数,创建 rpc 服务使用
  • build 和 script 的文件是用来生成服务器的代码,用来运行的

下面是 handle.go 的生成的代码:

package main  
  
import (  
   "context"  
   user "ephmeral.com/Kitex_example/kitex_gen/user"  
)  
  
// UserServiceImpl implements the last service interface defined in the IDL.type UserServiceImpl struct{}  
  
// CheckUser implements the UserServiceImpl interface.func (s *UserServiceImpl) CheckUser(ctx context.Context, req *user.CheckUserRequest) (resp *user.CheckUserResponse, err error) {  
   // TODO: Your code here...  
   return  
}  
  
// RegisterUser implements the UserServiceImpl interface.func (s *UserServiceImpl) RegisterUser(ctx context.Context, req *user.RegisterUserRequest) (resp *user.RegisterUserResponse, err error) {  
   // TODO: Your code here...  
   return  
}  
  
// UserInfo implements the UserServiceImpl interface.func (s *UserServiceImpl) UserInfo(ctx context.Context, req *user.UserInfoRequest) (resp *user.UserInfoResponse, err error) {  
   // TODO: Your code here...  
   return  
}

假设我们现在已经实现好了,那么只需要执行以下命令,对应的 user 服务就已经开启了。

$ sh build.sh
$ sh output/bootstrap.sh

小结

protobuf 是 RPC 框架必学的内容,了解相应的语法才能后面更好的使用 RPC 框架进行开发。