js中應用protocol buffer


前段時間公司項目需要用到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";
詳情連接: http://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html
 
 
優勢
 
支持向后兼容和向前兼容
消息字段修飾符 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(),
后台返回的數據反序列化后結果





 

閱讀更多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM