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