項目目的:
- 開發一個互動性的小遊戲,限於服務端經驗較少,故開始學習leaf架構,用戶端用cocos creator。
- 網路上此類可學習案例較少,故想一邊學習,一邊分享給後學者,謹以此勉勵自己!
環境搭建:
golang 環境搭建和cocos creator的環境搭建網上教程很多,不再贅述,golang IDE可使用Goland。
leaf架構地址:https://github.com/name5566/leaf.git
leaf入門教程:https://github.com/name5566/leaf/blob/master/TUTORIAL_ZH.md
example gihub地址:
server:https://github.com/ddkgo/LeafServerExample.git
client:https://github.com/ddkgo/LeafServerCocosClient.git
LeafServerExample搭建:
擷取:
git clone https://github.com/ddkgo/LeafServerExample.git
設定 LeafServerExample目錄到 GOPATH 環境變數後擷取 Leaf:
go get github.com/name5566/leaf
擷取protobuf支援:
go get github.com/golang/protobuf/proto
本文:
server接收和處理訊息:
1.建立一個msgPro.proto:
syntax = "proto3";package msg;message Hello { int32 id = 1; string name = 2;}
編譯 msgPro.proto 檔案(對此不瞭解?請先閱讀《在 Golang 中使用 Protobuf》一文)得到 msgPro.pb.go 檔案,命令如下:
protoc --go_out=. msgPro.proto
將msgPro.pb.go 放在LeafServerExample src/msg檔案夾下。
2.編輯 msg.go 檔案:
package msgimport ( "github.com/name5566/leaf/network/protobuf")// 使用 Protobuf 訊息處理器var Processor = protobuf.NewProcessor()func init() { Processor.Register(&Hello{})}
3.接下來處理 Hello 訊息的路由:
將 Hello 訊息路由到 game 模組中。開啟 LeafServerExample gate/router.go,敲入如下代碼:
package gateimport ( "server/msg" "server/game")func init() { // 這裡指定訊息 Hello 路由到 game 模組 msg.Processor.SetRouter(&msg.Hello{}, game.ChanRPC)}
4.處理訊息:
在 game 模組中處理 Hello 訊息了。開啟 LeafServerExample game/internal/handler.go,敲入如下代碼:
package internalimport ( "server/msg" "reflect" "github.com/name5566/leaf/gate" "github.com/name5566/leaf/log" "github.com/golang/protobuf/proto")func init() { // 向當前模組(game 模組)註冊 Hello 訊息的訊息處理函數 handleHello handler(&msg.Hello{}, handleHello)}func handler(m interface{}, h interface{}) { skeleton.RegisterChanRPC(reflect.TypeOf(m), h)}func handleHello(args []interface{}) { // 收到的 Hello 訊息 m := args[0].(*msg.Hello) // 訊息的寄件者 a := args[1].(gate.Agent) // 輸出收到的訊息的內容 log.Debug("hello %v", m.GetName()) retBuf :=&msg.Hello{ Name: *proto.String("client"), } // 給寄件者回應一個 Hello 訊息 a.WriteMsg(retBuf)}
client接收和處理訊息:
擷取protobufjs,在LeafServerCocosClient目錄下:
npm install protobufjs
1.proto編譯成靜態檔案:
把msgPro.proto 複製到LeafServerCocosClient node_modules.bin檔案夾下,把proto檔案編譯成靜態檔案使用:
pbjs -t static-module -w commonjs -o protocol.js msgPro.protopbts -o protocol.d.ts protocol.js
把protocol.js 和protocol.d.ts拷貝到LeafServerCocosClient assets\script\protocol檔案夾中.
2.建立websocket並串連:
建立netControl類:
const {ccclass, property} = cc._decorator;//定義全域的變數import * as onfire from "./libs/onfire/onfire.js"; //處理事件的類庫 import {netConfig} from './NetConfig'@ccclassexport class netControl extends cc.Component { private _sock:WebSocket = null //當前的webSocket的對象 connect(){ if(this._sock ==null || this._sock.readyState!==1){ //當前介面沒有開啟 //重新串連 this._sock = new WebSocket(netConfig.host+":"+netConfig.port) this._sock.onopen = this._onOpen.bind(this); this._sock.onclose = this._onClose.bind(this); this._sock.onmessage = this._onMessage.bind(this); } return this; } _onOpen(){ onfire.fire("onopen") } _onClose(err){ onfire.fire("onclose",err) let self = this let reVar = setInterval(function(){ // 先對重連過後的Websocket進行判斷,如果重連成功則斷開迴圈 if(self._sock.readyState == 1){ clearInterval(reVar) } self._sock = new WebSocket(netConfig.host+":"+netConfig.port) }, 5000) //每5秒嘗試一次重連 } _onMessage(obj){ onfire.fire("onmessage",obj) } send(msg){ if(this._sock.readyState == 1){ this._sock.send(msg); } } protoBufAddtag(tag: number,buffer: Uint8Array){ let addtag_buffer=new Uint8Array(buffer.length+2) let tagBinary = this.binary(tag,2) addtag_buffer.set(tagBinary,0) addtag_buffer.set(buffer.subarray(0,buffer.length),2) return addtag_buffer } parseProtoBufId(obj,callback:Function){ let blob:Blob = obj.data let reader = new FileReader(); reader.readAsArrayBuffer(blob); reader.onload = function(e) { let unit16 = new Uint16Array(e.target.result) let id = unit16[0] console.log("receive message id = "+id) let dataUnit8Array = new Uint8Array(e.target.result) dataUnit8Array = dataUnit8Array.slice(2) callback(id,dataUnit8Array) } } binary (num:number, Bits:number) { let resArry = [] let xresArry = [] let i=0; for(;num>0;){ resArry.push(num % 2) num=num/2 i++; } for(let j=i-1;j>=0;j--) xresArry.push(resArry[j]) if (Bits < xresArry.length) { console.log("位元小於二進位位元") } if (Bits) { for(let r = xresArry.length; r < Bits; r++) { xresArry.unshift(0) } } //return xresArry.join().replace(/,/g, ''); return xresArry }}
由於在 Leaf 中,預設的 Protobuf Processor 將一個完整的 Protobuf 訊息定義為如下格式:
-------------------------| id | protobuf message |-------------------------
所以在發送訊息時需要加上頭部id:
sendHello(name : string){ let protocolId = 0 let message = msg.Hello.create({ id:0,name:name }) let buffer = msg.Hello.encode(message).finish() //leaf 前兩位為協議序號,故需封裝一下 let addtag_buffer = this.netControl.protoBufAddtag(protocolId,buffer) this.netControl.send(addtag_buffer); console.log("sendToWS");}
接收到leaf返回的訊息時:
onMessage(obj){ //leaf 前兩位為協議序號,需要解一下啊協議序號 this.netControl.parseProtoBufId(obj,this.OnGameMessage.bind(this)) }
同樣的前兩位是leaf自動加上的id,需要處理下:
parseProtoBufId(obj,callback:Function){ let blob:Blob = obj.data let reader = new FileReader(); reader.readAsArrayBuffer(blob); reader.onload = function(e) { let unit16 = new Uint16Array(e.target.result) let id = unit16[0] console.log("receive message id = "+id) let dataUnit8Array = new Uint8Array(e.target.result) dataUnit8Array = dataUnit8Array.slice(2) callback(id,dataUnit8Array) } }
具體Helloworld 類:
import {netControl} from "./NetControl"import * as onfire from "./libs/onfire/onfire"import { msg } from "./protocol/protocol.js"const {ccclass, property} = cc._decorator@ccclassexport default class Helloworld extends cc.Component { @property(cc.Label) label: cc.Label = null; @property text: string = 'hello'; private msssageFire private netControl:netControl = null private proto onLoad(){ this.netControl = new netControl() } start () { // init logic this.label.string = this.text; this.netControl.connect(); this.msssageFire=onfire.on("onmessage",this.onMessage.bind(this)) } OnBtnSendHello(){ this.sendHello("ddk") } sendHello(name : string){ let protocolId = 0 let message = msg.Hello.create({ id:0,name:name }) let buffer = msg.Hello.encode(message).finish() //leaf 前兩位為協議序號,故需封裝一下 let addtag_buffer = this.netControl.protoBufAddtag(protocolId,buffer) this.netControl.send(addtag_buffer); console.log("sendToWS"); } onMessage(obj){ //leaf 前兩位為協議序號,需要解一下啊協議序號 this.netControl.parseProtoBufId(obj,this.OnGameMessage.bind(this)) } OnGameMessage(id: number,data: Uint8Array){ if(id===0){ console.log("get Hello message!"); let gameMsg = msg.Hello.decode(data) console.log(gameMsg) } } onDestroy(){ onfire.un(this.msssageFire); }}
:
server:
client:
goland build setting:
參考:
在 Leaf 中使用 Protobuf