这一章讲解cocoscreator与nodejs整合protobuf。
protobuf是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
protobuf是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
其实我觉得没有特殊的情况,cocoscreator与nodejs之间通信协议就用json就挺好的,方便,方便,方便。
本文使用protobuf地址是:protobuf链接。
先讲解一下思路:
前面我们讲解了,收到数据,会把数据解析成消息号+消息体,消息体是一串二进制数据,要转换成json,所以我们需要一个proto来解析,要快速找到那个可以解析该数据的proto,我们需要将消息号和proto做一个映射关系。
下面来看服务器端的实现。
proto文件数据为:
syntax = "proto2";
package login;
message login_req {
optional string acc = 1 [ default = "" ];
optional string pwd = 2 [ default = "" ];
optional int32 age = 3 [ default = 0 ];
}
message login_res {
optional bool result = 1 [ default = false ];
optional int32 uid = 2 [ default = 0 ];
optional string name = 3 [ default = "" ];
}
syntax = "proto2"; //版本
package login; //包名
optional表明字段是可选的,如果是required,则该字段是必须的,repeated用在是数组的情况。
加载proto核心代码段
var protoroot = {};
var files = fs.readdirSync("./proto");//读取proto文件夹下的所有proto文件
//console.log(files);
for (var i = 0; i < files.length; i++) {//遍历所有文件
var ext = path.extname(files[i]);//取出文件扩展名
if (ext === ".proto") {//如果是proto文件
//调用protobuf核心函数
var proot = protobuf.loadSync("./proto/" + files[i]);
for (var packagename in proot.nested) {
for (var msgname in proot.nested[packagename].nested) {
console.log(msgname);
var msgid = PCONST[msgname];
if (msgid != undefined) {
console.log(msgid);
//将proto解析器与消息号建立对应关系
protoroot[msgid] = proot.lookupType(msgname);
}
}
}
}
}
其中逻辑层代码有一些约定的写法,比如在const.js中
var CONST = module.exports;
CONST.SYS_MSG = {};
CONST.SYS_MSG.CONNECT = "_connect";
CONST.SYS_MSG.CLOSE = "_close";
CONST.LOGIC_MSG = {};
CONST.LOGIC_MSG.LOGIN_REQ = 1001;
CONST.LOGIC_MSG.LOGIN_RES = 2001;
CONST.PCONST = {};
CONST.PCONST["login_req"] = CONST.LOGIC_MSG.LOGIN_REQ;
CONST.PCONST["login_res"] = CONST.LOGIC_MSG.LOGIN_RES;
其中CONST.PCONST["login_req"]中的login_req与proto文件中的message login_req对应。
其中建立1001和login_req的关系,通过login_req与proto文件里message name同名则可建立映射关系。
//调用decode反序列化成json数据。
protoroot[msgid].decode(body_buffer)
//将格式为json的msg序列化成二进制数据
var data = this.protoroot[msgid].create(msg);
var bodyBuf = this.protoroot[msgid].encode(data).finish();
再看看客户端:
在protobufjs包的protobufjs\dist目录找到protobuf.js文件,稍加改动即可用于cocoscreator。如下
function fetch(filename, options, callback) {
......
if (typeof cc !== "undefined") {//判断是否是cocos项目
if (false) {//cc.sys.isNative
var content = jsb.fileUtils.getStringFromFile(filename);
callback(content === "" ? Error(filename + " not exits") : null, content);
} else {
//cc.log("cc.loader load 1 filename=" + filename);
//这里可以加载一个url图片 : "Host"+filename
// cc.loader.load(filename, function (error, result) {
// cc.log("error1=" + error + ",result = " + result + ",type=" + typeof result);
// // callback(null, result);
// });
//cc.log("cc.loader load 2");
// 这里h5会去加载resources目录下的文件 : "resources/"+ filename
// 这里filename一般不用指定扩展名,当然你也可以强制指定
//cc.TextAsset,
if (cc.path.extname(filename) === ".proto") {
var temp = filename.split(".");
filename = temp[0];
}
cc.loader.loadRes(filename, function (error, result) {
cc.log("loadRes : " + filename);
//cc.log("error2=" + error + ",result = " + result + ",type=" + typeof result);
if (error) {
callback(Error("status " + error))
} else {
callback(null, result.toString());
}
});
//cc.log("cc.loader load 3");
}
return;
}
......
}
伪装成nodejs项目即可。
客户端使用的方法基本和后端类似。建立msgid与proto解析的映射关系。
如下:
var CONST = cc;
CONST.MSG = {};
CONST.PTOTO = {};
CONST.PTOTO["login"] = {};
CONST.MSG.LOGIN_REQ = 1001;
CONST.PTOTO["login"][CONST.MSG.LOGIN_REQ] = "login_req";
CONST.MSG.LOGIN_RES = 2001;
CONST.PTOTO["login"][CONST.MSG.LOGIN_RES] = "login_res";
exports.loadProto = function (cb) {
var protoroot = {};
var count = 0;
var _loadProto = function (name, file_name) {
try {
protobuf.load(file_name, function (err, root) {
cc.log("protobuf.load return " + file_name);
if (err)
throw err;
try {
var objs = cc.PTOTO[name];
for (var msgid in objs) {
cc.log(msgid);
protoroot[msgid] = root.lookupType(objs[msgid]);
}
count--;
done();
}
catch (err) {
throw err;
}
});
}
catch (err) {
throw err;
}
};
var done = function () {
if (count === 0)
cb(protoroot);
};
var arr = Object.keys(cc.PTOTO);
if (arr.length == 0) done();
//遍历proto文件
for (var name in cc.PTOTO) {
var filename = "proto/" + name + "";
cc.log("load " + filename);
count++;
_loadProto(name, filename);
}
};
其中不同点在于CONST.PTOTO["login"]中的login是proto文件名。通过遍历CONST.PTOTO来遍历所有proto文件来建立映射关系,至于为什么这么做,读者可以自己琢磨一下。高手忽略本文。
//数据序列化
var message = msgroot.create(msg);
var buf = msgroot.encode(message).finish();
//数据反序列化
var msg_root = cc.protoRoot[msgid];
var msg = msg_root.decode(msg_buf);
总结一下,服务器端采用的同步加载的方式,客户端则使用的异步方式。逻辑主要是建立消息号和proto解析方法的对应关系,里面有一些常量约定的写法,为的就是方便使用protobuf。至于proto使用的一些参数和代码没有给大家详细讲解。大家可下载完整代码参考。谢谢支持。客户端代码地址、服务器端代码地址