前端後臺以及遊戲中使用Google Protocol Buffer詳解

javen205發表於2019-02-28

前端後臺以及遊戲中使用Google Protocol Buffer詳解

0、什麼是protoBuf

protoBuf是一種靈活高效的獨立於語言平臺的結構化資料表示方法,與XML相比,protoBuf更小更快更簡單。你可以用定義自己protoBuf的資料結構,用ProtoBuf編譯器生成特定語言的原始碼,如C++,Java,Python等,目前protoBuf對主流的程式語言都提供了支援,非常方便的進行序列化和反序列化。

特點:

  • 平臺無關、語言無關。
  • 二進位制、資料自描述。
  • 提供了完整詳細的操作API。
  • 高效能 比xml要快20-100倍
  • 尺寸小 比xml要小3-10倍 高可擴充套件性
  • 資料自描述、前後相容

1、下載protobuf的編譯器

目前最新版本為Protocol Buffers v3.5.1

2、配置環境變數

解壓 protoc-3.5.1-osx-x86_64.zip

Mac 配置環境變數 vi ~/.bash_profile 使其配置生效source ~/.bash_profile

#protobuf
export PROTOBUF_HOME=/Users/Javen/Documents/dev/java/protobuf/protoc-3.5.1-osx-x86_64
export PATH=$PATH:$PROTOBUF_HOME/bin
複製程式碼

Window 將bin新增到path 即可 例如:D:protobufprotoc-3.5.1-win32in

本文在Mac環境下編寫 Macwindow命令唯一的區別就是需要將protoc改成protoc.exe 前提是需要新增環境變數。

3、編寫一個proto檔案

檔案儲存為chat.protoproto檔案摘自t-io 讓天下沒有難開發的網路程式設計

syntax = "proto3";
package com.im.common.packets;

option java_package = "com.im.common.packets";  //設定java對應的package
option java_multiple_files = true; //建議設定為true,這樣會每個物件放在一個檔案中,否則所有物件都在一個java檔案中

/**
 * 聊天型別
 */
enum ChatType {
	CHAT_TYPE_UNKNOW = 0;//未知
	CHAT_TYPE_PUBLIC = 1;//公聊
	CHAT_TYPE_PRIVATE = 2;//私聊
}
/**
 * 聊天請求
 */
message ChatReqBody {
	int64 time = 1;//訊息傳送時間
	ChatType type = 2; //聊天型別
	string text = 3; //聊天內容
	string group = 4; //目標組id
	int32 toId = 5; //目標使用者id,
	string toNick = 6; //目標使用者nick
}

/**
 * 聊天響應
 */
message ChatRespBody {
	int64 time = 1;//訊息傳送時間
	ChatType type = 2; //聊天型別
	string text = 3; //聊天內容
	int32 fromId = 4; //傳送聊天訊息的使用者id
	string fromNick = 5; //傳送聊天訊息的使用者nick
	int32 toId = 6; //目標使用者id
	string toNick = 7; //目標使用者nick
	string group = 8; //目標組id
}
複製程式碼

4、編譯器對其進行編譯

4.1 編譯為Java

進入到專案的跟目錄執行以下編譯命名,com/im/common/packetschat.proto中的包名,chat.proto檔案放在com/im/common/packets下。

protoc  --java_out=./ com/im/common/packets/chat.proto
複製程式碼
4.2 編譯為JS
protoc --js_out=import_style=commonjs,binary:. chat.proto
複製程式碼

執行後會在當前資料夾中生成chat_pb.js 檔案,這裡面就是protobuf的API和一些函式。如果是Node.js 就可以直接使用了,如果想在瀏覽器(前端)中使用protobuf還需要做一些處理。

5、前端使用protobuf處理步驟

5.1 npm安裝需要的庫

chat_pb.js檔案的同級目錄下安裝引用庫

npm install -g require
npm install google-protobuf
npm install -g browserify
複製程式碼
5.2 使用browserify對檔案進行編譯打包

編寫指令碼儲存為exports.js

var chatProto = require(`./chat_pb`);  
module.exports = {  
DataProto: chatProto  
}
複製程式碼

執行命令 browserify exports.js > chat.jschat_pb.js檔案進行編譯打包生成chat.js後就可以愉快的使用了。

6、protobuf使用示例

6.1 前端(JavaScript)中使用protobuf
<script src="./chat.js"></script>
<script type="text/javascript">
    var chatReqBody = new proto.com.im.common.packets.ChatReqBody();
    chatReqBody.setTime(new Date().getTime());
    chatReqBody.setText("測試");
    chatReqBody.setType(1);
    chatReqBody.setGroup("Javen");
    chatReqBody.setToid(666);
    chatReqBody.setTonick("Javen205");

    var bytes = chatReqBody.serializeBinary();  
    console.log("序列化為位元組:"+bytes);
    var data = proto.com.im.common.packets.ChatReqBody.deserializeBinary(bytes); 
    console.log("反序列化為物件:"+data);  
    console.log("從物件中獲取指定屬性:"+data.getTonick());
    console.log("物件轉化為JSON:"+JSON.stringify(data));  

</script>
複製程式碼
6.2 Java中使用protobuf

java中要用protobuf,protobuf與json相互轉換,首先需要引入相關的jar,maven的pom座標如下

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>
複製程式碼
public static void test() {
		try {
			JsonFormat jsonFormat = new JsonFormat();
			ChatRespBody.Builder builder = ChatRespBody.newBuilder();
			builder.setType(ChatType.CHAT_TYPE_PUBLIC);
			builder.setText("Javen 測試");
			builder.setFromId(1);
			builder.setFromNick("Javen");
			builder.setToId(110);
			builder.setToNick("Javen.zhou");
			builder.setGroup("Javen");
			builder.setTime(SystemTimer.currentTimeMillis());
			ChatRespBody chatRespBody = builder.build();
			//從protobuf轉json
			String asJson = jsonFormat.printToString(chatRespBody);
			System.out.println("Object to json "+asJson);
			
			byte[] bodybyte = chatRespBody.toByteArray();
			//解碼是從byte[]轉換為java物件
			ChatRespBody parseChatRespBody = ChatRespBody.parseFrom(bodybyte);
			asJson = jsonFormat.printToString(parseChatRespBody);
			System.out.println("bodybyte to json "+asJson);
			
			//從json轉protobuf
			ChatRespBody.Builder _builder = ChatRespBody.newBuilder();
			jsonFormat.merge(new ByteArrayInputStream(asJson.getBytes()), _builder);
			ChatRespBody _chatRespBody = _builder.build();
			asJson = jsonFormat.printToString(_chatRespBody);
			System.out.println("json to protobuf "+asJson);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
複製程式碼
6.3 QQ玩一玩中使用protobuf

chat.js中的var global = Function(`return this`)();修改為

// var global = Function(`return this`)();

var global = (function(){
  return this;
})()

複製程式碼

BK.Script.loadlib(`GameRes://qqPlayCore.js`);
BK.Script.loadlib(`GameRes://tio/chat.js`);

function test() {
	var ws = new BK.WebSocket("ws://127.0.0.1:9326?group=test&name=Javen");
	ws.onOpen = function(ws) {
		BK.Script.log(1, 0, "onOpen.js");
		BK.Script.log(1, 0, "1.readyState = " + ws.getReadyState());

		var time = 0;
		BK.Director.ticker.add(function(ts, duration) {
			time = time + 1;
			if (time % 100 == 0) {
				// ws.send("phone test" + time);
				var chatReqBody = new proto.com.im.common.packets.ChatReqBody();
				chatReqBody.setTime(new Date().getTime());
				chatReqBody.setText("phone test" + time);
				chatReqBody.setType(1);
				chatReqBody.setGroup("test");
				var bytes = chatReqBody.serializeBinary();
				ws.send(bytes);
			}
		});
	};
	ws.onClose = function(ws) {
		BK.Script.log(1, 0, "onClose.js");
		BK.Script.log(1, 0, "1.readyState = " + ws.getReadyState());
	};
	ws.onError = function(ws) {
		BK.Script.log(1, 0, "onError.js");
		BK.Script.log(1, 0, "1.readyState = " + ws.getReadyState());
		BK.Script.log("onError.js.js getErrorCode:" + ws.getErrorCode());
		BK.Script.log("onError.js getErrorString:" + ws.getErrorString());
	};
	ws.onMessage = function(ws, event) {
		if (!event.isBinary) {
			var str = event.data.readAsString();
			BK.Script.log(1, 0, "text = " + str);
		} else {
			var buf = event.data;
			//將遊標pointer重置為0
			buf.rewind();
			var ab = new ArrayBuffer(buf.length);
			var dv = new DataView(ab);
			while (!buf.eof) {
				dv.setUint8(buf.pointer, buf.readUint8Buffer());
			}
			var chatRespBody = proto.com.im.common.packets.ChatRespBody.deserializeBinary(ab);
			var msg = chatRespBody.getFromnick() + " 說: " + chatRespBody.getText();
			BK.Script.log(1, 0, "text = " + msg);
		}
	};
	ws.onSendComplete = function(ws) {
		BK.Script.log(1, 0, "onSendComplete.js");
	};
	ws.connect();
}

test();
複製程式碼
6.4 Eget中使用protobuf
外掛下載

egret有提供將proto檔案生成JS以及TS的工具

npm install protobufjs -g
npm install @egret/protobuf -g
複製程式碼
操作步驟

1、在白鷺專案的根目錄中新建protobuf資料夾,再在protobuf資料夾中新建protofile資料夾

2、將proto檔案放到protofile資料夾中

3、依次執行pb-egret addpb-egret generate

將會自動完成以下操作:

1、在tsconfig.json中的include節點中新增protobuf/**/*.d.ts

2、在egretProperties.json中的modules節點新增

{
"name": "protobuf-library",
"path": "protobuf/library"
},
{
"name": "protobuf-bundles",
"path": "protobuf/bundles"
}
複製程式碼

3、在protobuf資料夾中自動生成bundles以及library資料夾裡面包含了我們需要的js以及ts

專案中能使用

處理髮送訊息

 private sendReq(text:string,group:string){
        var chatReqBody = new com.im.common.packets.ChatReqBody();
        chatReqBody.time = new Date().getTime();
        chatReqBody.text = text;
        chatReqBody.type = com.im.common.packets.ChatType.CHAT_TYPE_PUBLIC;
        chatReqBody.group = group;
        let data = com.im.common.packets.ChatReqBody.encode(chatReqBody).finish();
        this.sendBytesData(data);
    }

    private sendBytesData(data:Uint8Array){
        this.socket.writeBytes(new egret.ByteArray(data));
    }
複製程式碼

處理接收訊息

 private onReceiveMessage(e:egret.Event):void {

        //建立 ByteArray 物件
        var byte:egret.ByteArray = new egret.ByteArray();
        //讀取資料
        this.socket.readBytes(byte);
        let buffer = new Uint8Array(byte.buffer);
        let chatRespBody =  com.im.common.packets.ChatRespBody.decode(buffer);
        
        // this.trace("收到資料:"+JSON.stringify(chatRespBody));
        this.trace(chatRespBody.fromNick+" 說: "+chatRespBody.text);
    }
複製程式碼
6.5 Cocos Creator中使用protobuf

Cocos Creator中使用protobuf與前端中使用protobuf操作步驟基本一樣,只是在Cocos Creator中需要將JS匯入為外掛

5.2中編譯生成的JS匯入到工程的指令碼資料夾中,開啟Cocos Creator就會提示您是否要將指令碼設定為外掛。

前端後臺以及遊戲中使用Google Protocol Buffer詳解
前端後臺以及遊戲中使用Google Protocol Buffer詳解
專案中能使用

與6.1前端中使用方式一樣。

start: function () {
        cc.log("start");
        let chatReqBody = new proto.com.im.common.packets.ChatReqBody();
        chatReqBody.setTime(new Date().getTime());
        chatReqBody.setText("測試");
        chatReqBody.setType(1);
        chatReqBody.setGroup("Javen");
        chatReqBody.setToid(666);
        chatReqBody.setTonick("Javen205");

        let bytes = chatReqBody.serializeBinary();
        cc.log("序列化為位元組:" + bytes);
        let data = proto.com.im.common.packets.ChatReqBody.deserializeBinary(bytes);
        cc.log("反序列化為物件:" + data);
        cc.log("從物件中獲取指定屬性:" + data.getTonick());
        cc.log("物件轉化為JSON:" + JSON.stringify(data));
    },
複製程式碼

到這裡如何使用protobuf就介紹完了,個人能力有限如有錯誤歡迎指正。你有更好的解決方案或者建議歡迎一起交流討論,如有疑問歡迎留言。

相關文章