技术笔记: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

相关文章
|
11月前
|
前端开发 Android开发 数据安全/隐私保护
安卓MVI架构真的来了?动手试着封装吧(一)上
安卓MVI架构真的来了?动手试着封装吧(一)
262 0
|
3天前
|
Ubuntu Linux vr&ar
IM跨平台技术学习(十二):万字长文详解QQ Linux端实时音视频背后的跨平台实践
本文详细记录了新版QQ音视频通话在 Linux 平台适配开发过程中的技术方案与实现细节,希望能帮助大家理解在 Linux 平台从 0 到 1 实现音视频通话能力的过程。
16 2
|
2月前
|
存储 Java 应用服务中间件
即时通讯技术文集(第36期):《跟着源码学IM》系列专题 [共12篇]
为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第36期。
39 0
|
11月前
|
Android开发 开发者 Kotlin
安卓MVI架构真的来了?动手试着封装吧(一)下
安卓MVI架构真的来了?动手试着封装吧(一)
125 1
|
11月前
|
Android开发 开发者 Kotlin
安卓MVI架构真的来了?动手试着封装吧(二)上
安卓MVI架构真的来了?动手试着封装吧(二)
113 0
安卓MVI架构真的来了?动手试着封装吧(二)上
|
11月前
|
API Android开发 Kotlin
安卓MVI架构真的来了?动手试着封装吧(三)下
安卓MVI架构真的来了?动手试着封装吧(三)
92 0
|
存储 XML 开发框架
Unity Metaverse(三)、Protobuf & Socket 实现多人在线
使用Scoket TCP和Protobuf通信协议实现多人在线。
244 1
Unity Metaverse(三)、Protobuf & Socket 实现多人在线
|
存储 JSON 前端开发
Golang+Protobuf+PixieJS 开发 Web 多人在线射击游戏(原创翻译)
Golang+Protobuf+PixieJS 开发 Web 多人在线射击游戏(原创翻译)
164 0
Golang+Protobuf+PixieJS 开发 Web 多人在线射击游戏(原创翻译)
|
消息中间件 存储 小程序
教你用纯Java实现一个即时通讯系统(附源码)(上)
教你用纯Java实现一个即时通讯系统(附源码)(上)
教你用纯Java实现一个即时通讯系统(附源码)(上)
|
消息中间件 存储 负载均衡
教你用纯Java实现一个即时通讯系统(附源码)(下)
教你用纯Java实现一个即时通讯系统(附源码)(下)
教你用纯Java实现一个即时通讯系统(附源码)(下)