前段时间公司项目需要用到protocol buffer数据传输协议,这是什么东西,根本没接触过,好好的json干嘛不用?怀着好奇心去了解学习,最后顺利运用。下面是一些是经验,希望能帮到一些人。
首先我们要知道什么是protocol buffer
protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。类似于XML,JSON这样的数据表示语言,ProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,格式有点类似XML,可以自己定义数据格式,它是一种二进制格式允许你使用规范的语言定义一个模式。
基本语法
普通消息
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } |
指定字段规则
所指定的消息字段修饰符必须是如下之一:
² required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;
² optional:消息格式中该字段可以有0个或1个值(不超过1个)。
² repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。
l 标量数值类型
一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:
.proto类型 | Java 类型 | C++类型 | 备注 |
double | double | double | |
float | float | float | |
int32 | int | int32 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。 |
int64 | long | int64 | 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。 |
uint32 | int[1] | uint32 | Uses variable-length encoding. |
uint64 | long[1] | uint64 | Uses variable-length encoding. |
sint32 | int | int32 | 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。 |
sint64 | long | int64 | 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。 |
fixed32 | int[1] | uint32 | 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 |
fixed64 | long[1] | uint64 | 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 |
sfixed32 | int | int32 | 总是4个字节。 |
sfixed64 | long | int64 | 总是8个字节。 |
bool | boolean | bool | |
string | String | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 |
bytes | ByteString | string | 可能包含任意顺序的字节数据。 |
Optional的字段和默认值
optional int32 result_per_page = 3 [default = 10]; |
导入定义
import "myproject/other_protos.proto"; |
优势
支持向后兼容和向前兼容
消息字段修饰符required,optional,repeated
性能好,效率高
protobuf在序列化化后数据大小上,同一条消息protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一,同样的带宽,protobuf的效率明显不是其他格式能比拟的。
下面是thrift和protobuf性能对比
由于thrift功能较protobuf丰富,因此单从序列化机制上进行性能比较,按照序列化后字节数、序列化时间、反序列化时间三个指标进行,对thrift的二进制、压缩、protobuf三种格式进行对比。
测试方法:取了15000+条样本数据,分别写了三个指标的测试程序,在我自己的电脑上执行,其中时间测试循环1000次,总的序列化/反序列化次数1500W+。
平均字节数:
thrift二进制 | 535 |
thrift压缩 | 473 |
protobuf | 477 |
序列化(1500W次)时间(ms):
thrift二进制 | 306034 |
thrift压缩 | 304256 |
protobuf | 177652 |
反序列化(1500W次)时间(ms):
thrift二进制 | 287972 |
thrift压缩 | 315991 |
protobuf | 157192 |
thrift的时间测试可能不是很准,由于thrift产生代码的复杂性,编写的测试代码为了适应其接口,在调用堆栈上可能有一些额外开销。
js应用
首先我们需要下载对应的三方库,去github上下载https://github.com/dcodeIO/protobuf.js或者npm
install protobufjs安装
主要用到的js有3个,引入到项目中即可
<script src="js/proto/Long.min.js"></script>
<script src="js/proto/bytebuffer.js"></script>
<script src="js/proto/protobuf.js"></script>
协议用的是websocket 需要设置
wWebsocket.binaryType = "arraybuffer" ;
ajax的话设置
这里以一个简单的秘钥交换exchange_key.proto举例
//package protocol;
option java_package = "com.xingdian.seeyou.net.protocol";
enum ENCRYPT_TYPE {
RC4 = 1;
};
message ExchangeKeyReq {
required bytes N = 1;
required int32 E = 2;
}
message ExchangeKeyRes {
required bytes key = 1;
required ENCRYPT_TYPE type = 2;
}
message ExchangeKey {
optional ExchangeKeyReq exchange_key_req = 1;
optional ExchangeKeyRes exchange_key_res = 2;
}
这里要package protocol这个东西,后台给过来的protobuf文件中可能有这一行,我们是加载不了message的,这段文字我们一定要注释掉。对于这个message对于js比较棘手一点的可能是bytes这个数据类型了,下面会说明
//create ProtoBuf
if (typeof dcodeIO === 'undefined' || !dcodeIO.ProtoBuf) {
throw (new Error(
"ProtoBuf.js is not present. Please see www/index.html for manual setup instructions."));
}
var ProtoBuf = dcodeIO.ProtoBuf;
ExchangeKey = loadProto("exchange_key.proto","ExchangeKey");
FrontendPk = ProtoBuf.loadProtoFile("proto/frontend.proto").build("FrontendPk");
/**
* @param {string}
* @param {string}
* @return {protoBuf object}
*/
function loadProto(fileName,objectName){
return ProtoBuf.loadProtoFile("proto/"+fileName).build(objectName);
}
/**序列化
* @return {buff}
*/
function createExchangeKeyReq(){
crypt = new JSEncrypt( {default_key_size: 1024} );
var crKey = crypt.getKey();
var bytesN = crKey.n.toByteArray();
var ExchangeKeyReq = loadProto("exchange_key.proto","ExchangeKeyReq");
var exchangeKeyReq = new ExchangeKeyReq();
exchangeKeyReq.setE(crKey.e);
// 129byte
exchangeKeyReq.setN((new Int8Array(bytesN,1)).buffer);
var exchangeKey = new ExchangeKey();
exchangeKey.setExchangeKeyReq(exchangeKeyReq);
// console.log("ExchangeKey ProtoBuf对象数据:");
// console.log(exchangeKey);
var msgDec = exchangeKey.toBuffer();
var length = msgDec.byteLength;
var dataView = new DataView(new ArrayBuffer(length+8));
var msgDecView = new DataView(msgDec);
//set
dataView.setUint32(0,length,false);
dataView.setUint32(4,10000,false);
for(var i = 0;i< length;i++){
dataView.setUint8(i+8,msgDecView.getUint8(i));
}
return dataView.buffer;
}
/**反序列化
* @param {[xhr.response]}
* @return {[string]}
*/
function parseRc4(evt){
var resBuffer = evt.data.slice(8);
var resExchangeKey = ExchangeKey.decode(resBuffer);
var exchangeKeyRes = resExchangeKey.getExchangeKeyRes();
var rc4Key = exchangeKeyRes.getKey();
var rc4KeyBase64 = rc4Key.toBase64();
rc4key = crypt.decrypt(rc4KeyBase64);
// console.log("decrypt RC4 Secret: "+RC4key);
return rc4key;
}
对于经常要使用到的message对象 我们抽取出来,每次都去load很影响性能。上面提到了对于bytes类型的数据js怎么处理,这里需要用到js中的buff对象去匹配,不然其他的数据类型都会异常
exchangeKeyReq.setN((new Int8Array(bytesN,1)).buffer);
序列化,后面代码是因为消息需要添加8个字节的包头,不然可以直接返回exchangeKey.toBuffer(),
来源:CSDN
作者:恋残o风
链接:https://blog.csdn.net/pigisyou147/article/details/52260044