校招后端面经--传输协议
WXG使用的传输协议是protobuf,这篇作为扩展了解一下,是给面试加分的一个环节。
1. 文本文件和二进制文件
根据该博客进行整理
- 文件实际上包括了两个部分,控制信息和内容信息。纯文本文件仅仅是没有控制格式信息罢了,实际文本文件就是一种特殊的二进制文件(所有的数据类型都是char)
- 二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放,也就是说存放的是数据的原形式
- 文本文件是把数据的以终端形式输出到磁盘上存放,也就是说存放的是数据的终端形式。(终端形式也就是每一个都是字符)
- 字符数据本身在内存中就经过了编码,所以无论是二进制还是文本形式都是一样的,而对于非字符数据来说,例如int i= 10;如果用二进制来进行存储的话为1010,但是如果需要用文本形式来进行存储的话就必须进行格式化编码(对1和0分别编码,即形式为’'1和’0’分别对应的码值)
- 文本文件编码基于字符定长,译码容易些;二进制文件编码是变长的,所以它灵活,存储利用率要高些,译码难一些
2. 文本协议和二进制协议
-
文本协议如HTTP协议,redis协议,都是以字符的形式传数据:
{ "code":10, "msg":"succeed", "info": { "user":"1234@qq.com", "name":"test", "group":"test", "tel":"18888888" } }
对于code的值10,起始接收到的也是两个字符,只是因为这里没有
""
,所以一些json的解析会对值进行转换成对应的某种语言类型(如这里的10会被转为int类型) -
二进制协议如protobuf, jce,protobuf的消息结构是一系列序列化后的Tag-Value对。其中Tag由数据的field和writetype组成,value为源数据编码后的二进制数据。
message Person { int32 id = 1; string name = 2; }
其中id字段的field为1,writetype为int32类型对应的序列号。编码后对应的tag为(field_number << 3)| write_type = 0000 1000,其中低位的3位标识writetype,其他位标志field
-
json版本化
始终添加新属性,并且不要删除或者重名现有属性,例如:
{ "version": "1.0", "foo": true }
如果想将“foo”属性重命名为“bar”,不要只是重命名它,而应该添加一个新属性:
{ "version": "1.1", "foo": true, "bar": true }
由于从未删除属性,因此基于旧版本的客户端将继续工作。这种方法的缺点是,json可能随着时间的推移而变得臃肿,并且必须要维护旧的属性
-
protobuf版本化
通过使用可选字段来避免协议的版本号,不需要版本化
当原始的protobuf消息改变时,最理想的方式时使用这个消息的各个端都同步更新。为了让没有更新的端也能保持正常工作,我们需要做到:
- 不可以增加或删除必须的(required)字段,这样才能使新旧端都能工作
- 对已经存在的字段的标签数字不可以更改。
- 可以删除可选(optional)或重复(repeated)字段,但其序号不可以再被使用。最好是将这个字段重命名,比如加上OBSOLETE_,让后面的开发人员不会再用这个序列号
- 可以添加新的可选或重复字段,但是必须使用新的标签数字,必须是之前的字段所没有用过的
注意:
- 需要注意的是新的可选消息不会在旧的消息中显示,所以需要在新的客户端或服务端使用has_严格检查他们是否存在
- 在.proto文件中可以提供一个缺省值,如果没有缺省值,就会有一个类型相关的默认缺省值:对于字符串就是空字符串;对于布尔值则是false;对于数字类型默认为0
- 如果你添加了新的重复字段,新客户端或服务端不会告诉你这个字段为空,旧客户端或服务端也不会包含has_标志
-
protobuf反射机制
#include <google/protobuf/descriptor.h> #include <google/protobuf/message.h> #include <string> google::protobuf::Message* createMessage(const std::string& type_name) { google::protobuf::Message* message = NULL; const google::protobuf::Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name); if (descriptor) { const google::protobuf::Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); if (prototype) { message = prototype->New(); } } return message; }
这样使用type_name传参调用DescriptorPool的FindMessageTypeByName拿到一个const类型的Descriptor,然后通过Descriptor的GetPrototype获得const类型的Message,最后经由const Message的New即可得到一个可自由读写的Message对象。但是需要注意的是对这个对象是动态创建的,调用者在使用完毕后必须动态释放它,随意推荐使用shared_ptr管理该对象。