技术笔记:leaf和cocoscreator游戏实战(一)使用protobuf完成通讯

简介: 技术笔记:leaf和cocoscreator游戏实战(一)使用protobuf完成通讯

版权声明:本文为博主原创文章,未经博主允许不得转载。


项目目的:


开发一个交互性的小游戏,限于服务端经验较少,故开始学习leaf框架,客户端用cocos creator。


网络上此类可学习案例较少,故想一边学习,一边分享给后学者,谨以此勉励自己!


环境搭建:


golang 环境搭建和cocos creator的环境搭建网上教程很多,不再赘述,golang IDE可使用Goland。


leaf框架地址:


leaf入门教程:


example gihub地址:


server:


client:


LeafServerExample搭建:


获取:


git clone


设置 LeafServerExample目录到 GOPATH 环境变量后获取 Leaf:


go get github.com/name5566/leaf


获取protobuf支持:


go get github.com/golang/protobuf/proto


正文:


server接收和处理消息:


1.创建一个lobby.proto:


syntax = "proto3";


package msg;


message Test {


string Test = 2;


}


编译 lobby.proto 文件(对此不了解?请先阅读《在 Golang 中使用 Protobuf》一文)得到 lobby.pb.go 文件,命令如下:


protoc --go_out=. lobby.proto


将lobby.pb.go 放在LeafServerExample src/msg文件夹下。


2.编辑 msg.go 文件:


package msg


import (


//代码效果参考:http://www.jhylw.com.cn/311438105.html

"github.com/name5566/leaf/network/protobuf"

)


// 使用 Protobuf 消息处理器


var Processor = protobuf.NewProcessor()


func init() {


Processor.Register(&Test {})


}


3.接下来处理 Test 消息的路由:


将 Test 消息路由到 game 模块中。打开 LeafServerExample gate/router.go,敲入如下代码:


package gate


import (


"server/msg"


"server/game"


)


func init() {


// 这里指定消息 Test路由到 game 模块


msg.Processor.SetRouter(&msg.Test {}, game.ChanRPC)


}


4.处理消息:


在 game 模块中处理 Test 消息了。打开 LeafServerExample game/internal/handler.go,敲入如下代码:


package internal


import (


"server/msg"


"reflect"


"github.com/name5566/leaf/gate"


"github.com/name5566/leaf/log"


"github.com/golang/protobuf/proto"


)


func init() {


// 向当前模块(game 模块)注册Test消息处理函数 handleHello


handler(&msg.Test {}, handleHello)


}


func handler(m interface{}, h interface{}) {


skeleton.RegisterChanRPC(reflect.TypeOf(m), h)


}


func handleHello(args 【】interface{}) {


// 收到的 Test 消息


m := args【0】.(msg.Test )


// 消息的发送者


a := args【1】.(gate.Agent)


// 输出收到的消息的内容


log.Debug("hello %v", m.GetTest ())


retBuf :=&msg.Test {


Test : proto.String("client"),


}


// 给发送者回应一个 Test 消息


a.WriteMsg(retBuf)


}


client接收和处理消息:


获取protobufjs,在LeafServerCocosClient目录下:


npm install protobufjs


1.proto编译成静态文件:


把lobby.proto 复制到LeafServerCocosClient node_modules.bin文件夹下,把proto文件编译成静态文件使用:


pbjs -t static-module -w commonjs -o protocol.js lobby.proto


pbts -o protocol.d.ts protocol.js


把protocol.js 和protocol.d.ts拷贝到LeafServerCocosClient assets\script\protocol文件夹中.


2.创建websocket并连接:


新建netControl类:


import as onfire from "./libs/onfire/onfire.js"; //处理事件的类库


import netConfig from './NetConfig.js'


export default class netControl {


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);


this._sock.binaryType = "arraybuffer";


}


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.IntToUint8Array(tag,16);


let tagUnit8 = new Uint8Array(tagBinary);


addtag_buffer.set(tagUnit8,0);


addtag_buffer.set(buffer.subarray(0,buffer.length),2);


return addtagbuffer;


}


parseProtoBufId(obj: MessageEvent) :{id:number,data:Uint8Array} {


let arrayBuffer:ArrayBuffer = obj.data;


let dataUnit8Array = new Uint8Array(arrayBuffer);


let id = this.Uint8ArrayToInt(dataUnit8Array.slice(0,2));


console.log("receive message id = "+id);


dataUnit8Array = dataUnit8Array.slice(2);


return {id: id,data:dataUnit8Array};


}


IntToUint8Array (num: number, Bits: number) :number【】{


let resArry = 【】;


let xresArry = 【】;


let binaryStr:string = num.toString(2);


for(let i=0;i

resArry.push(parseInt(binaryStr【i】));


if (Bits) {


for(let r = resArry.length; r < Bits; r++) {


resArry.unshift(0);


}


}


let resArryStr= resArry.join("");


for(let j=0;j[span class="hljs-title class">Bits;j+=8)


xresArry.push(parseInt(resArryStr.slice(j,j+8),2));


return xresArry;


}


/**


Uint8Array【】转int


相当于二进制加上4位。同时,使用|=号拼接数据,将其还原成最终的int数据


@param uint8Ary Uint8Array类型数组


@return int数字


/


Uint8ArrayToInt(uint8Ary:Uint8Array){


let retInt:number =0;


for(let i= 0;i

retInt|=(uint8Ary【i】 [ (8(uint8Ary.length-i-1)));


return retInt;


}


}


由于在 Leaf 中,默认的 Protobuf Processor 将一个完整的 Protobuf 消息定义为如下格式:


-------------------------


| id | protobuf message |


-------------------------


所以在发送消息时需要加上头部id:


sendMessage(xyName:string,data:{}){


let protocolId = netConfig.ProtocolId【xyName】;


let message = msg【xyName】.create(data);


let buffer = msg【xyName】.encode(message).finish();


//leaf 前两位为协议序号,故需包装一下


let addtag_buffer = this.netControl.protoBufAddtag(protocolId,buffer);


this.netControl.send(addtag_buffer.buffer);


console.log("sendToWS");


}


sendHello(name: string){


this.sendMessage("Test",{ Test:name });


console.log("sendHello");


}


接收到leaf返回的消息时:


onMessage(obj: MessageEvent){


if(obj.data instanceof ArrayBuffer){


//leaf 前两位为协议序号,需要解一下啊协议序号


let retdata = this.netControl.parseProtoBufId(obj);


let id = retdata.id;


let data = retdata.data;


this.netMessageCtrl.dealMessage(id,data);


}


}


同样的前两位是leaf自动加上的id,需要处理下:


parseProtoBufId(obj: MessageEvent) :{id:number,data:Uint8Array} {


let arrayBuffer:ArrayBuffer = obj.data;


let dataUnit8Array = new Uint8Array(arrayBuffer);


let id = this.Uint8ArrayToInt(dataUnit8Array.slice(0,2));


console.log("receive message id = "+id);


dataUnit8Array = dataUnit8Array.slice(2);


return {id: id,data:dataUnit8Array};


}


具体Lobby 类:


import netControl from "./NetControl"


import as onfire from "./libs/onfire/onfire"


import NetMessageCtrl from './NetMessageCtrl';


const {ccclass, property} = cc._decorator


@ccclass


export default class Lobby extends cc.Component {


@property(cc.Label)


label: cc.Label = null;


@property(cc.Node)


regNode: cc.Node = null;


@property(cc.Node)


loginNode: cc.Node = null;


@property(cc.Node)


PersistRootNode: cc.Node = null;


@property


text: string = 'hello';


private msssageFire


private netControl:netControl = null


private netMessageCtrl:NetMessageCtrl = null


onLoad(){


this.netControl = new netControl();


this.netMessageCtrl = new NetMessageCtrl(this.netControl);


cc.game.addPersistRootNode(this.PersistRootNode);


}


start () {


// init logic


this.label.string = this.text;


this.netControl.connect();


this.msssageFire=onfire.on("onmessage",this.onMessage.bind(this));


}


onMessage(obj: MessageEvent){


if(obj.data instanceof ArrayBuffer){


//leaf 前两位为协议序号,需要解一下啊协议序号


let retdata = this.netControl.parseProtoBufId(obj);


let id = retdata.id;


let data = retdata.data;


this.netMessageCtrl.dealMessage(id,data);


}


}


onDestroy(){


onfire.un(this.msssageFire);


}


onBtnSendHello(){


this.netMessageCtrl.sendHello("ddk");


}


onBtnRegister(){


this.regNode.active = true;


}


onBtnLogin(){


this.loginNode.active = true;


}


}


wx.sendSocketMessage(Object object)


参数


Object object


属性类型默认值是否必填说明支持版本


data


string/ArrayBuffer



需要发送的内容


success


function



接口调用成功的回调函数


fail


function



接口调用失败的回调函数


complete


function



接口调用结束的回调函数(调用成功、失败都会执行)


this._sock.binaryType = "arraybuffer"


截图:


server:


client:


goland build setting:


参考:


在 Leaf 中使用 Protobuf

相关文章
|
2天前
|
开发工具 图形学 Android开发
【推荐100个unity插件之6】利用Photon PUN2框架最快最简单实现多人在线游戏
【推荐100个unity插件之6】利用Photon PUN2框架最快最简单实现多人在线游戏
5 0
|
1月前
|
API Android开发 C++
【字节跳动大牛系列教学】Android源码剖析之Framwork层消息传递
【字节跳动大牛系列教学】Android源码剖析之Framwork层消息传递
|
存储 前端开发 中间件
iOS 模块化进阶整理记录(上)
iOS 模块化进阶整理记录(上)
422 0
iOS 模块化进阶整理记录(上)
|
存储 前端开发 测试技术
iOS 模块化进阶整理记录(下)
iOS 模块化进阶整理记录(下)
158 0
iOS 模块化进阶整理记录(下)
|
Java
动力节点Java项目实例教程,用Java实现的在线聊天小项目,适合巩固Java基础(附资料源码)
对于学习完JavaSE的小伙伴,急需要做一个JavaSE的综合性项目来练练手,提升综合编码能力,那么该JavaSE实现的DQ在线聊天项目项目就非常适合你来练手,该项目的主要目的是让学完Java SE的同学对Socket网络编程、IO流、线程及线程池等知识进行一些实战综合运用。
200 0
动力节点Java项目实例教程,用Java实现的在线聊天小项目,适合巩固Java基础(附资料源码)
|
存储 缓存 API
iOS网络编程之一——iOS网络框架简介
iOS网络编程之一——iOS网络框架简介
195 0
iOS网络编程之一——iOS网络框架简介
|
Dart 算法 JavaScript
Flutter中的Tree Shaking机制初探(科普文)
Tree Shaking是一种DCE技术。Flutter中,同样有这样的Tree Shaking机制来减小最终产出的包大小
3279 0
Flutter中的Tree Shaking机制初探(科普文)
|
XML 存储 Shell
Go 开发关键技术指南 | 带着服务器编程金刚经走进 2020 年(内含超全知识大图)
从问题本身出发,不局限于 Go 语言,探讨服务器中常常遇到的问题,最后回到 Go 如何解决这些问题,为大家提供 Go 开发的关键技术指南。我们将以系列文章的形式推出《Go 开发的关键技术指南》,共有 4 篇文章,本文为第 3 篇。
Go 开发关键技术指南 | 带着服务器编程金刚经走进 2020 年(内含超全知识大图)
Firefly - 分布式游戏服务器端框架
Firefly 是免费、开源、稳定、快速扩展、能 “热更新”的分布式游戏服务器端框架,采用Python编写,基于 Twisted 框架开发。它包括了开发框架和数据库缓存服务等各种游戏服务器基础服务,节省大量游戏开发的工作时间,真正做到让使用者把精力放在游戏玩法逻辑上。
1301 0
|
Java Maven Android开发
从零开始仿写一个抖音App——Apt代码生成技术、gradle插件开发与protocol协议
本文首发于简书——何时夕,搬运转载请注明出处,否则将追究版权责任。交流qq群:859640274 有人说我标题党,也怪我开始决定写的时候没有注意标题,也没想到会有这么多阅读量,的确会生出一些是非出来。
2003 0