protobuf使用细节
这东西和什么语言无关,是中间接口描述语言IDL,
具体细节不说了,在参考链接里都有的,这里记录下我关注的细节
[toc]
字段
消息对象的字段 组成主要是:字段 = 字段修饰符 + 字段类型 +字段名 +标识号
类型是有个表格来描述具体的字节占用 (原来的表格有java我不太关注就去掉了)
.proto类型 | C++类型 | Go | 备注 |
---|---|---|---|
double | double | float64 | |
float | float | float32 | |
int32 | int32 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。 | |
int64 | int64 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。 | |
uint32 | uint32 | Uses variable-length encoding. | |
uint64 | uint64 | Uses variable-length encoding. | |
sint32 | int32 | 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。 | |
sint64 | int64 | 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。 | |
fixed32 | uint32 | 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 | |
fixed64 | uint64 | 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 | |
sfixed32 | int32 | 总是4个字节。 | |
sfixed64 | int64 | 总是8个字节。 | |
bool | bool | ||
string | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | |
bytes | string | 可能包含任意顺序的字节数据。 使用和string一样的,传参数也是string |
如果字段更新类型,转换规则可以看字段更新部分
标识号分配
[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。要为将来有可能添加的/频繁出现的标识号预留一些标识号。
字段更新
如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
- 不要更改任何已有的字段的数值标识号。
- 所添加的任何字段都必须是optional或repeated的。这就意味着任何使用“旧”的消息格式的代码序列化的消息可以被新的代码所解析,因为它们 不会丢掉任何required的元素。应该为这些元素设置合理的默认值,这样新的代码就能够正确地与老代码生成的消息交互了。类似地,新的代码创建的消息 也能被老的代码解析:老的二进制程序在解析的时候只是简单地将新字段忽略。然而,未知的字段是没有被抛弃的。此后,如果消息被序列化,未知的字段会随之一 起被序列化——所以,如果消息传到了新代码那里,则新的字段仍然可用。注意:对Python来说,对未知字段的保留策略是无效的。
- 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
- 一个非required的字段可以转换为一个扩展,反之亦然——只要它的类型和标识号保持不变。
- int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
- sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
- string和bytes是兼容的——只要bytes是有效的UTF-8编码。
- 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
- fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
说实话,兼容不是特别关注,主要关注标识号改动部分,最开始开发要做预留,然后改动标识号不要改动已有的,找缝隙加上就行
字段的操作(CURD)
一般来说,消息的字段会自动生成set_xxx方法
package message; message MessageRequest {
required string msg = 10;
}
对应的
message::MessageRequest::MessageRequest req;
req.set_msg("yoyoyo");
下面列举几个特殊场景
repeated字段的更新
repeat可以理解成数组,处理方法也多了几步, 会提供一个接口
package message;
message Pair {
required string key;
required string value;
}
message MessageRequest {
required string msg = 10;
required int32 seq = 20;
repeated Pair pairs = 30;
}
对应的修改
message::MessageRequest req;
std::vector<message::Pair> pairs;
for (auto& v: pairs) {
//type: message::MessageRequest::field*
auto pair = req.add_pairs();
pair->set_key('kk');
pair->set_value('vv');
}
有人说,通过repeated字段来更新数据,当返回为空的时候,可能分不清是应该清空还是保持不变
需要加字段来纠正这个歧义,这里不细说了,感觉就是想要便捷(返回空)强行创造的歧义。约定好的话没啥问题,不需要加字段
package message;
message Pair {
required string key;
required string value;
}
message MessageRequest {
required string msg = 10;
required int32 seq = 20;
repeated Pair pairs = 30;
optional bool modify = 31; //如果是0个field modify是1那就是清空,modify是0那就是没更新
}
嵌套结构的消息, 生成了set_allocated_xxx方法, 没有普通的set_xxx方法
这里传进set_allocated_xxx 的对象必须是new好的,这个不太好用,protobuf内部会帮你delete,你自己也可以调用release_xx(最好别)
也可以用mutable_xx 内部帮你new好,你自己赋值,类似这样的
mutable_xx()->set_xx(/*xx*/);
也可以用copyfrom比较省心,其实都不太好用,尽量别嵌套
optional字段会生成has_xx方法 但proto3不支持怎么办
https://stackoverflow.com/questions/42622015/how-to-define-an-optional-field-in-protobuf-3
message Foo {
int32 bar = 1;
oneof optional_baz {
int32 baz = 2;
}
}
用oneof来封装一层 proto3新版本也支持optional了
merge
支持字段的merge操作,设置fieldMask
参考链接
- 官方文档的翻译 https://colobu.com/2015/01/07/Protobuf-language-guide/
- https://www.jianshu.com/p/e06ba6249edc 这篇感觉就是上一篇的细化
- 这有个整理的更细致的 https://juejin.im/post/6844903687089831944
- Repeated 修改 http://lambda.hk/protobuf/2015/06/05/protobuf-repeated/
- repeated 歧义 https://blog.csdn.net/love_newzai/article/details/6929430
- 嵌套 https://blog.csdn.net/xiaxiazls/article/details/50118161
- copyFrom https://blog.csdn.net/u014088857/article/details/53291545
- https://jergoo.gitbooks.io/go-grpc-practice-guide/content/chapter1/protobuf-go.html go的使用说明