很早以前就據說過protobuf,對此協議的評價也一直很高。可是以前接觸到的項目一直沒有使用這個協議,都是直接跟後端約定好數據包格式(協議號+數據包大小),而後將對應的數據結構體轉換爲數據字節流進行數據的傳輸。最近接觸的一個新項目,使用到了protobuf協議,項目前端使用的是cocos2dx-lua,正好前段時間在用cocos creator進行項目開發,因此本着在開發中學習的想法,用cocos creator再簡單的將項目實現一邊,主要是加入protobuf協議。前端
在搜索引擎中搜索 cocos creator + protobuf ,獲得的資料都比較舊,並且我試着參考網上給到的資料,一直失敗,因此仍是要根據creator、protobuf的版原本作必定的調整才行。node
本文中使用到的環境是:cocos creator 2.0.10版本,protobuf 6.8.6版本,下載地址。git
一、github
下載好protobuf以後,導入到creator中,在提示是否導入爲插件的時候選擇是,而後再將容許編輯器加載勾上,否則在編輯過程當中一直會提示 protobuf 未定義之類的錯誤:web
二、後端
將protobuf導入到編輯器以後,就能夠參考官方的例子,來編寫代碼了,首先看一下官方給的proto文件:api
// awesome.proto package awesomepackage; syntax = "proto3"; message AwesomeMessage { string awesome_field = 1; // becomes awesomeField }
具體的protobuf語法,能夠回頭再去了解一下。這裏有幾個關鍵信息後續會用到,包名(awesomepackage)、消息名稱(AwesomeMessage)。將proto文件放到resource目錄(這個目錄是creator規定用於加載動態導入資源的目錄),以前lua這邊是將.proto文件轉成了.pb文件(具體爲何要轉,.pb和.proto有什麼不一樣,還沒了解清楚),因此一開始我也是直接去加載.pb文件,一直加載不了,後來才發現網上的教程都是加載的.proto文件。websocket
官方給的例子,加載方式是這樣的:數據結構
protobuf.load("awesome.proto", function(err, root) { if (err) throw err; // Obtain a message type var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage"); }
直接運行的話,會報錯,相似: return callback(Error("status " + xhr.status)); ,參考 這篇文章 ,修改protobuf.js,搜索 function fetch 修改一下:socket
function fetch(filename, options, callback) { if (typeof options === "function") { callback = options; options = {}; } else if (!options) options = {}; if (!callback) return asPromise(fetch, this, filename, options); // eslint-disable-line no-invalid-this // 判斷是不是cocos項目 if (typeof cc !== "undefined"){ if (cc.sys.isNative){ var content = jsb.fileUtils.getStringFromFile(filename) callback(content === "" ? Error(filename + " not exits") : null, content) } else{ cc.loader.loadRes(filename, cc.TextAsset, function (error, result) { if (error) { callback(Error("status " + error)) } else { callback(null, result); } }) } return } // if a node-like filesystem is present, try it first but fall back to XHR if nothing is found. if (!options.xhr && fs && fs.readFile) ... }
主要就是那個判斷是否cocos環境的地方。
我參考這個方式,試着加載了一下,root有值,可是按照這個例子,root.lookupType 返回的是null,後來參考論壇的教程,改了一下加載方式,直接使用cocos提供的加載資源的api加載:
cc.loader.loadRes(pbFiles, cc.TextAsset, function (err, protos) { if (err) { bg.Log.e("load proto error ==> ", err) return } let pr = protobuf.parse(tex) bg.Log.i(pr) let rs = pr.root.lookupType("awesomepackage.AwesomeMessage") bg.Log.i(rs) let payLoad = { awesome_field:"hello sdfsefewg"} let msg = rs.create(payLoad) bg.Log.i("msg ", msg) let buf = rs.encode(msg).finish() bg.Log.i("buf ", buf) let decode = rs.decode(buf) bg.Log.i("decode ", JSON.stringify(decode)) })
使用這種方案,能夠正確的加載到,打印出來的值都是正常的。
若是有多個.proto文件,則能夠用cc.loader.loadResArray批量加載,在加載結果裏面,將protobuf.parse解析的結果,以文件名爲key存儲起來:
for (let proto of protos) { window.protoMessageMap[proto._name] = protobuf.parse(proto) }
在多.proto文件下,發、收消息的時候,使用哪一個協議,我這裏使用的是一個笨方法:
// 發送消息 let protocal = "包名.messageName" for (let k in protoMessageMap) { let obj = protoMessageMap[k] let found = obj.root.lookup(protocal) if (null != found) { let bodyMsg = found.create(data) let body = found.encode(bodyMsg).finish()
break } } // 接收消息 let protocal = "包名.messageName" for (let k in protoMessageMap) { let obj = protoMessageMap[k] let found = obj.root.lookup(protocal) if (null != found) { let msg = found.decode(pbmsg.body) break } }
遍歷一邊全部加載進來的 .proto,而後再作後續操做。這個地方有個問題就是,若是消息名重複了,就會有問題。後期再看看有沒有什麼解決方案吧。
另外,creator用websocket進行消息通信時,接收到的數據包用protobu解包,須要將數據包轉一下格式:
found.decode(new Uint8Array(event.data))
否則protobuf會報錯。