本文基于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(); //解码一个消息