个人知识库知识地图

准备工作

protobuf官方文档

编译 Protobuf,需要安装工具:

brew install autoconf
brew install automake
brew install libtool

运行下面脚本进行编译:

./autogen.sh
./configure
make
make install

检查protobuf是否安装成功:

protoc --version

成功打印版本号则安装成功

libprotoc 3.21.12

Protobuf 支持的数据类型

Protobuf定义了一套基本数据类型

.proto TypeNotesC Type
doubledouble
floatfloat
int32使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代int32
uint32使用变长编码uint32
uint64使用变长编码uint64
sint32使用变长编码,这些编码在负值时比int32高效的多int32
sint64使用变长编码,有符号的整型值。编码时比通常的int64高效。int64
fixed32总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。uint32
fixed64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。uint64
sfixed32总是4个字节int32
sfixed64总是8个字节int64
boolbool
string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。string
bytes可能包含任意顺序的字节数据。string

protobuf 枚举类型

当需要定义一个消息类型的时候,可能想为一个字段指定“预定义值序列”中的一个值,这时候可以通过枚举实现。

syntax = "proto3";//指定版本信息,不指定会报错

//枚举消息类型,使用enum关键词定义,一个电话类型的枚举类型
enum PhoneType {
    MOBILE = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
    HOME = 1;
    WORK = 2;
}

// 定义一个电话消息
message PhoneNumber {
    string number = 1; // 电话号码字段
    PhoneType type = 2; // 电话类型字段,电话类型使用PhoneType枚举类型
}

protobuf 数组类型

在protobuf消息中定义数组类型,是通过在字段前面增加repeated关键词实现,标记当前字段是一个数组。

  1. 整数数组的例子:
message Msg {
  // 只要使用repeated标记类型定义,就表示数组类型。
  repeated int32 arrays = 1;
}
  1. 字符串数组
message Msg {
  repeated string names = 1;
}

protobuf 消息嵌套

我们在各种语言开发中类的定义是可以互相嵌套的,也可以使用其他类作为自己的成员属性类型。

protobuf中同样支持消息嵌套,可以在一个消息中嵌套另外一个消息,字段类型可以是另外一个消息类型。

  • 引用其他消息类型的用法
// 定义Result消息
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3; // 字符串数组类型
}
// 定义SearchResponse消息
message SearchResponse {
  // 引用上面定义的Result消息类型,作为results字段的类型
  repeated Result results = 1; // repeated关键词标记,说明results字段是一个数组
}
  • 消息嵌套

类似类嵌套一样,消息也可以嵌套。

例子:

message SearchResponse {
  // 嵌套消息定义
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  // 引用嵌套的消息定义
  repeated Result results = 1;
}
  • import导入其他proto文件定义的消息

我们在开发一个项目的时候通常有很多消息定义,都写在一个proto文件,不方便维护,通常会将消息定义写在不同的proto文件中,在需要的时候可以通过import导入其他proto文件定义的消息。

例子:

保存文件: result.proto

syntax = "proto3";
// Result消息定义
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3; // 字符串数组类型
}

保存文件: search_response.proto

syntax = "proto3";
// 导入Result消息定义
import "result.proto";

// 定义SearchResponse消息
message SearchResponse {
  // 使用导入的Result消息
  repeated Result results = 1;
}

protobuf map类型

protocol buffers支持字典类型定义。

  • map语法
map<key_type, value_type> map_field = N;

key_type可以是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型)。请注意,枚举不是有效的key_type

value_type 可以是除另一个映射之外的任何类型。

  • map的例子
syntax = "proto3";
message Product {
    string name = 1; // 商品名
    // 定义一个k/v类型,key是string类型,value也是string类型
    map<string, string> attrs = 2; // 商品属性,键值对
}

Map 字段不能使用repeated关键字修饰。

定义消息

消息(message),在protobuf中指的就是我们要定义的数据结构

语法

syntax = "proto3";

message 消息名 {
    消息体
}

syntax关键词定义使用的是proto3语法版本,如果没有指定默认使用的是proto2

message关键词,标记开始定义一个消息,消息体,用于定义各种字段类型。

syntax = "proto3";

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

定义了一个Person消息,这个消息有3个字段,name和tel是字符串类型,age是int32类型。

我们通常将protobuf消息定义保存在.proto为后缀的文件中。

分配标识符

在消息定义中,每个字段后面都有一个唯一的数字,这个就是标识号。

这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变,每个消息内唯一即可,不同的消息定义可以拥有相同的标识号。

保留标识号(Reserved) 如果你想保留一些标识号,留给以后用,可以使用下面语法:

message Person {
  reserved 2, 15, 9 to 11; // 保留2,15,9到11这些标识号
}

如果使用了这些保留的标识号,protocol buffer编译器会输出警告信息。

注释

往.proto文件添加注释,支持C/C++/java风格的双斜杠(//) 语法格式。

例子:

message Person {
  string name = 1;  //名字
  int32 age = 2;    //年龄
  string tel = 3;
}

为消息定义包

我们也可以为消息定义包。

proto协议文件编译为OC文件

protoc --proto_path=. --objc_out=.  Person.proto

参数的形式为: --参数命令名=参数

--proto_path=.​ 指出proto文件所在的根目录是哪里, 如果用​.​说明是当前目录

​--objc_out=.​ 指出 生成目录在哪里, 如果用​.​说明是当前目录

最后跟 proto文件的名称

iOS项目中使用

  • 使用cocoapods引入protobuf
pod ‘Protobuf’
  • 新建一个文件夹,拷贝已经有的.proto文件至此。用终端进入该文件夹,执行
protoc --plugin=/usr/local/bin/protoc-gen-objc *.proto --objc_out="./"
  • 文件夹下便可以看到xxx.pb.h和xxxx.pb.m这两个文件了。将生成的 .h 和 .m 文件添加到工程中,编译

序列化

我们在使用socket与服务器通信时,是以二进制数据流的形式进行传输的,因此我们要将Pbdata.pbobjc创建的对象转为二进制数据流,这个过程就称之为序列化

反序列化

服务器给我们传输的也是二进制数据流,所以我们需要不断拼接数据包,直到拼接成服务器规定的大小,将其转为OC对象,那么这个过程就是反序列化

如果这篇内容对你有帮助,可以请我喝杯咖啡。