本文基于node 6.9.x 使用的protobuf.js的版本 5.0.2
因?yàn)?/span>layabox 1.6.x引擎自帶的protobuf.js的版本是5.0.1,考慮兩邊兼容,所以我在node服務(wù)器端使用5.0.2
我的目標(biāo)用js同時(shí)實(shí)現(xiàn)手機(jī)端和服務(wù)器端,不用搞兩套語(yǔ)言了,使用protobuf就不重造車(chē)輪了
首先下載安裝protobufjs https://github.com/dcodeIO/protobuf.js/tree/5.0.2
用npm命令 npm install protobufjs@5
安裝完成后,就可以使用了protobuf.js了
為了實(shí)戰(zhàn),我這里使用了多個(gè)proto文件
放到工程的proto目錄下面
msghead.proto
---------------------------------------分割線開(kāi)始-------------------------------------------------------
syntax = "proto3";
package lz;
enum MsgType { //消息類(lèi)型
Request = 0; //請(qǐng)求類(lèi)弄
Answer = 1; //響應(yīng)類(lèi)型
Notice = 2; //通知類(lèi)型
NotMessage = 3; //不是消息
}
//消息頭
message MsgHead
{
required int32 flag = 1 [default = 1978]; //消息標(biāo)志,固定值為1978
required int32 id = 2; //MessageID
required MsgType type = 3 [default = 0]; //消息類(lèi)型
}
---------------------------------------分割線結(jié)束-------------------------------------------------------
noticemsgid.proto
---------------------------------------分割線開(kāi)始-------------------------------------------------------
syntax = "proto3";
package lz;
//這里的消息ID,只是通知消息
enum NoticeMsgID
{
GameOver = 1;
};
---------------------------------------分割線結(jié)束-------------------------------------------------------
msgid.proto
---------------------------------------分割線開(kāi)始-------------------------------------------------------
syntax = "proto3";
package lz;
//這里的消息ID,對(duì)應(yīng)的消息定義必須成對(duì)出現(xiàn)
enum MsgID
{
HelloWorld = 1;
};
---------------------------------------分割線結(jié)束-------------------------------------------------------
test.proto
---------------------------------------分割線開(kāi)始-------------------------------------------------------
syntax = "proto3";
package lz.msg;
message ReqHelloWorld //MsgID = HelloWorld
{
required int32 id = 1; // ID
required string str = 2; // str
optional int32 opt = 3; //optional field
}
message AnsHelloWorld
{
required int32 Result = 1; //處理結(jié)果
optional string error_msg = 2; //錯(cuò)誤信息
}
message NoticeGameOver
{
required int32 Result = 1; //
};
---------------------------------------分割線結(jié)束-------------------------------------------------------
msg.proto
---------------------------------------分割線開(kāi)始-------------------------------------------------------
//這里把所有的proto文件包含進(jìn)來(lái)
package lz;
syntax = "proto3";
import "msghead.proto";
import "msgid.proto";
import "noticemsgid.proto";
import "test.proto";
---------------------------------------分割線結(jié)束-------------------------------------------------------
當(dāng)然,也可以把這些放到一個(gè)文件中.
安裝完成protobufjs后,在node_module/.bin/有pbjs.cmd 可以生成對(duì)應(yīng)的proto的js代碼
如:pbjs D:\newgame\proto\msg.proto -t commonjs >d:\tmp\a.js
import fs from "fs";
import Protobuf from "protobuf";
import ByteBuffer from "ByteBuffer";
//消息管理器類(lèi),用于消息分發(fā),這里只是demo,所以這個(gè)只有簡(jiǎn)單的功能
class MsgManager {
constructor(){
this._notice_map = new Map();
this._request_map = new Map();
this._answer_map = new Map();
this._buffMsg = new ByteBuffer(4192);
}
get notice_map() { return this._notice_map; }
get request_map() { return this._request_map; }
get answer_map() { return this._answer_map;}
//消息編碼 并放到this._buffMsg中
encode_msg(msgDef, data) {
let msgHead = this.MsgHead; //在關(guān)聯(lián)的時(shí)候,將消息ID,類(lèi)型與消息體關(guān)聯(lián)了
msgHead.id = msgDef._msgHead.id;
msgHead.type = msgDef._msgHead.type;
this._buffMsg.clear(); //清除緩沖
this._buffMsg.writeInt32(0); //預(yù)寫(xiě)入消息包的大小 4字節(jié)包體大小 + 2字節(jié)消息包頭大小 + 消息包頭數(shù)據(jù) + 消息體數(shù)據(jù)
let msgHeadSizeOffset = this._buffMsg.offset; //這里記錄消息包頭大小偏移位置
this._buffMsg.writeInt16(0); //預(yù)寫(xiě)入消息包頭大小
msgHead.encode(this._buffMsg); //消息頭編碼
this._buffMsg.writeInt16(this._buffMsg.offset - msgHeadSizeOffset, msgHeadSizeOffset); //寫(xiě)往下正確的消息包頭大小
let msg = new msgDef(data);
msg.encode(this._buffMsg); //消息體編碼
let msgSize = this._buffMsg.offset;
this._buffMsg.writeInt32(this._buffMsg.offset, 0); //寫(xiě)入正確的消息包大小
console.log(this._buffMsg);
}
//解碼處理
decode_msg() {
let bb = new ByteBuffer(this._buffMsg.offset);
this._buffMsg.copyTo(bb, 0, 0, this._buffMsg.offset); //取出要解碼的數(shù)據(jù) 在解碼的過(guò)程中,數(shù)據(jù)實(shí)際的數(shù)據(jù)大于解碼需要的數(shù)據(jù),會(huì)拋出異常,所以在這里先把這個(gè)消息的數(shù)據(jù)復(fù)制出來(lái),再解碼,注:這里,暫時(shí)沒(méi)有做拆包處理
let msgSize = bb.readInt32(); //讀取包大小
let headSize = bb.readInt16(); //取消息頭大小
let head = this.lz.MsgHead.decode(bb, headSize-2); //取得消息頭數(shù)據(jù)
console.log(head);
let msg_map;
switch(head.type) //根據(jù)類(lèi)型,取消息映射表
{
case this.MsgType.Request:
msg_map = this.request_map;
break;
case this.MsgType.Answer:
msg_map = this.answer_map;
break;
case this.MsgType.Notice:
msg_map = this.notice_map;
break;
}
let msg = msg_map.get(head.id).decode(bb); //根據(jù)id,解碼數(shù)據(jù)
console.log(msg);
}
}
//這里是處理消息的關(guān)聯(lián)
var msgMgr = new MsgManager();
function msg_process(messageMgr) {
//讀取proto文件,并生成相應(yīng)的代碼
let protodata = fs.readFileSync("./proto/msg.proto").toString();
let lz = Protobuf.loadProto(protodata,null,"./proto/msg.proto").build("lz"); //生成package lz下面對(duì)像消息數(shù)據(jù)
messageMgr.MsgType = lz.MsgType; //消息類(lèi)型定義
messageMgr.MsgID = lz.MsgID; //消息id定義
messageMgr.NoticeMsgID = lz.NoticeMsgID; //通知消息定義
messageMgr.Msg = lz.msg; //所有的消息定義
messageMgr.MsgHead = new lz.MsgHead(); //消息頭對(duì)像,對(duì)于發(fā)送的時(shí)候,減少new的次數(shù)
messageMgr.MsgHead.flag = 1978;
messageMgr.lz = lz;
//關(guān)聯(lián):請(qǐng)求響應(yīng) 消息
for(let msgName in messageMgr.MsgID)
{
let msgReq = "Req" + msgName; //請(qǐng)求消息名稱(chēng)
let msgAns = "Ans" + msgName; //響應(yīng)消息名稱(chēng)
let msgID = messageMgr.MsgID[msgName]; //對(duì)應(yīng)的消息ID
let req = lz.msg[msgReq]; //請(qǐng)求消息的消息定義對(duì)象
let ans = lz.msg[msgAns]; //響應(yīng)消息的消息定義對(duì)象
req._msgHead = {id:msgID, type:messageMgr.MsgType.Request}; //生成消息頭
ans._msgHead = {id:msgID, type:messageMgr.MsgType.Answer};
messageMgr.request_map.set(msgID, req); //建立ID與消息的關(guān)聯(lián)
messageMgr.answer_map.set(msgID, ans);
}
//關(guān)聯(lián)通知消息
for(let msgName in messageMgr.NoticeMsgID)
{
let msgNotice = "Notice" + msgName;
let msgID = messageMgr.NoticeMsgID[msgName];
let notice = lz.msg[msgNotice];
notice._msgHead = {id:msgID, type:messageMgr.MsgType.Notice};
messageMgr.notice_map.set(msgID, notice);
}
console.log(messageMgr.request_map, messageMgr.answer_map, messageMgr.notice_map);
}
msg_process(msgMgr); //關(guān)聯(lián)消息
msgMgr.encode_msg(msgMgr.Msg.ReqHelloWorld, { id: 1999, str: "測(cè)試發(fā)送中文", opt: 0}); //編碼一個(gè)消息
msgMgr.decode_msg(); //解碼一個(gè)消息