深入了解使用egret.WebSocket

概念

本教程不讲解TCP/IP协议,Socket属于哪层,消息包体怎么设计等,主讲 egret.WebSocket 使用示例 与 protobuf 使用示例。

在使用egret.WebSocket之前需要简单讨论了解目前几种通信模式。

HTTP

291848223297979.png

网站中常见的一种传输协议,用于访问页面或资源时,向页面所在的服务器发送一个 HTTP 请求。服务器识别请求,返回响应数据并关闭连接。这过程中客户端不请求,服务器不能主动推送消息到客户端。早些的游戏通过轮训以及 AJAX 实现了不需要手动刷新程序内部轮训请求的伪的长连接。这显然是一个非常不明智的方式。可以想象一下聊天室或人物移动场景中,如果我们使用 HTTP 会是一种什么情况。大量的请求与响应报头额外的数据、延迟不断发生、传输带宽压力不断增加,这对于ARPG等类型游戏是致命的。主要适合对即时性要求不高的游戏类型。

Socket

291848366893526.png

端游中常见的一种传输协议,套链接。需要了解 Socket 的同学百度一下,它是一个长连接的协议。在完成握手后,连接会一直开着,直到客户端或服务器明确予以关闭。在这过程中,服务器能主动的推送消息到客户端,消息格式可以是进制流以及自定义格式等。后期由于FLASH的兴起,页游中绝大多数都在使用。可以想象一下聊天室或人物移动场景中,我们使用 socket 会是一种什么情况。没有额外的数据、主动的消息推送、低延迟等等。

WebScoket

早期 HTML 中并没有提供 socket 的支持,大型页游项目依靠于 Flash 提供的 Socket API 。随着 HTML5 的制定与完善,WebSocket 被各大浏览器厂商所支持。

WebScoket 与 Socket 的区别在于前者提供了完善的API以及握手的机制,而后者是抽象出来的一种概念,具体的实现对于各种语言都可能不同,例如:我们需要自定义协议体,控制缓存区,连接确认方式等。而在 WebSocket 中,每个消息的传输规范都是定义好的,如消息以 0x00 字节开头,以 0xff 结尾,中间数据采用 UTF-8 编码格式,第一次握手必须使用 ws://xxxwss://xxx 进行,在握手成功后将协议升级为 WebSocket 协议,进行双工的通信。第一次请求走的是 HTTP 请求。由于各种规范的定义与实现,旧有的服务器 Socket 并不适用于 WebSocket 。

实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如:

egret.WebSocket 使用示例

早期参与或制作游戏项目,对下图一定不陌生,定义消息长度位、消息号以及消息读取规范,客户端根据协议规范以字节形式读取包体:

291848529397227.png

HML5 的 WebSocket 传输中,并没有定义进制流的传送读取。 egret.WebSocket 中对 HTML5 中 WebSocket 进行封装,实现了对于进制流的传输。

egret.WebSocket 默认是字符串形式接受数据,创建一个 egret.WebSocket 非常简单,由于 egret.WebSocket 对字节流传输的实现,服务器与客户端旧有的协议非常方便移植。以下示例演示了创建 egret.WebSocket :

1.修改项目文件 egretProperties.json 中的 modules ,增加 {"name": "socket"}

2.在项目所在目录执行一次编译引擎 egret build -e

this.socket = new egret.WebSocket();
//设置数据格式为二进制,默认为字符串
this.socket.type = egret.WebSocket.TYPE_BINARY;
//添加收到数据侦听,收到数据会调用此方法
this.socket.addEventListener(egret.ProgressEvent.SOCKET_DATA,   this.onReceiveMessage, this);
//添加链接打开侦听,连接成功会调用此方法
this.socket.addEventListener(egret.Event.CONNECT, this.onSocketOpen, this);
//添加链接关闭侦听,手动关闭或者服务器关闭连接会调用此方法
this.socket.addEventListener(egret.Event.CLOSE, this.onSocketClose, this);
//添加异常侦听,出现异常会调用此方法
this.socket.addEventListener(egret.IOErrorEvent.IO_ERROR, this.onSocketError, this);
//连接服务器
this.socket.connect("echo.websocket.org", 80);

当触发 egret.Event.CONNECT 侦听方法 onSocketOpen 时连接服务器成功,可以进行数据发送接收。我们创建一个字节数组,通过writeType写入字符串类型,布尔类型,整形,设置指针为开始0,调用 this.socket.writeBytes 写入数据进行数据发送:

var byte:egret.ByteArray = new egret.ByteArray();
byte.writeUTF("Hello Egret WebSocket");
byte.writeBoolean(false);
byte.writeInt(123);
byte.position = 0;
this.socket.writeBytes(byte, 0, byte.bytesAvailable);
this.socket.flush();

当触发 egret.ProgressEvent.SOCKET_DATA 侦听方法 onReceiveMessage() 时数据接收成功,创建一个字节数组并将 socket 中当前数据读入其中,与发送方式类似,接收使用 readType :

var byte:egret.ByteArray = new egret.ByteArray();
this.socket.readBytes(byte);
var msg:string = byte.readUTF();
var boo:boolean = byte.readBoolean();
var num:number = byte.readInt();

protobuf 使用示例

百度百科 protocolbuffer 介绍,protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了三种语言的实现:java、c++ 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

protobuf 被适用于非常多的生产环境中,也出现了各种语言的版本,方便了数据的移植与可维护性。它在部分语言项目中有一定缺陷,如随着项目的不断迭代会产生较多的数据结构类机器码增加项目体积。

这里以第三库的形式加入对 protobufjs 的支持。想了解第三方集成的同学点击:集成第三方JavaScript库

示例下载见教程尾部:

1.拷贝示例项目 libs 目录下 protobuf 目录到新项目所在 libs 目录。

2.拷贝 libsrc 目录下 protobuf 目录到新项目所在 protobuf 目录。

3.项目 egretProperties.json 中增加相关内容。

egretProperties.json:
{
"document_class": "Main",
"modules": [
    {
        "name": "core"
    },
    {
        "name": "version"
    },
    {
        "name": "res"
    },
    {
        "name": "socket"
    },
    {
        "name": "protobuf",
        "path": "libsrc/protobuf"
    }
],
"egret_version": "2.0.2"
}

编译引擎,完成对protobuf配置。

在 resource\assets\proto下 ,新建数据文件并命名为 common.proto 。在其中定义我们需要传输的类对象。这个文件在实际生产环境中是服务端客户端公用的,可以有单个或多个根据具体项目而定。通过工具生产对应语言的访问类,如name.ts,并引入项目中,通过 new 或其他方式创建实例。可惜的是目前还没有egret语言所使用的生成工具。

首先在 common.proto 内定义结构体,了解语法点击这里 。我们定义一个简单结构,如:

message Common {
    required uint32 id = 1;
    required string text = 2;
}

在 resource.js 中我们引入 common.proto 文件,为了方便,在初始化进行加载。也可以使用 RESDepot 工具进行导入。

当文件被加载后,进行数据设置之前需要四步:

1.获取资源数据文件。

2.解码并创建对象构造器。

3.创建需要的数据结构类。

4.实例化数据结构类。

设置与读取示例,如下代码:

var proto: string = RES.getRes("common_proto");
var builder:any = dcodeIO.ProtoBuf.loadProto(proto);   
var clazz:any = builder.build("Common");                      
var data:any = new clazz();                                                 
data.set("id",1);//可以使用data.id=1;
data.set("text","oops");//可以使用data.text=oops; 

console.log("id=" + data.get("id"));
console.log("oops=" + data.get("text"));

我想我写这到这里,不只是为了创建一个文件,序列化数据,反序列化数据,然后创建个实例吧。 好吧,我们继续往下讲,下面就是我们具体使用 egret.WebSocket 发送数据。 这是我们使用它的关键。

在使用上例中 builder.build("Common") 得到对象构造器中提供了序列化的方法 toArrayBuffer() 通过 egret.ByteArray 写入序列化进行传输,在实际的环境中,还需要涉及到一些长度位,校验,消息号等这里不做讨论。发送示例,如下代码:

var arraybuffer: ArrayBuffer = data.toArrayBuffer();
var len: number = arraybuffer.byteLength;
var btyearray:egret.ByteArray=new egret.ByteArray(arraybuffer);
if(len > 0)
{ 
    this.socket.writeBytes(btyearray);
    this.socket.flush();
}

接收数据, 我们代码中一直出现 ArrayBuffer 这是JS中一种用于二进制数据存储的类型,与我们的 ByteAarry 相似(ByteAarry封装了ArrayBuffer) 通过 DataView 提供的接口,转换为我们可以使用的 ByteAarray 数据,如下代码:

var msgBuff: ArrayBuffer;
var btyearray: egret.ByteArray = new egret.ByteArray();
this.socket.readBytes(btyearray);
var len = btyearray.buffer.byteLength;
var dataView = new DataView(btyearray.buffer);
var pbView = new DataView(new ArrayBuffer(len));
for(var i = 0;i < len;i++) {
    pbView.setInt8(i,dataView.getInt8(i));
}
msgBuff = pbView.buffer;

var proto: string = RES.getRes("common_proto");
var builder:any = dcodeIO.ProtoBuf.loadProto(proto); 
var clazz:any = builder.build("Common");
        
var data: any = clazz.decode(msgBuff);    
console.log("decodeData id=" + data.get("id"));
console.log("decodeData oops=" + data.get("text"));

项目示例:下载

最后,感谢董刚同学提供的protobuf库。

转载于:https://www.cnblogs.com/yangxiao/p/4686729.html

<think>我们正在讨论如何停止Egret中的Timer对象。根据引用,我们创建Timer时通常会添加事件监听,并调用start()方法启动。要停止Timer,我们需要使用Timer类提供的stop()方法。 步骤: 1. 首先,确保你有一个对Timer对象的引用(比如在类中定义为成员变量)。 2. 在需要停止的地方调用timer.stop()方法。 3. 注意:停止后,如果再次调用start(),计时器会重新开始(如果repeatCount未达到,则继续之前的计数;如果设置为0,则无限重复)。 另外,为了确保不再接收事件,可以在停止后移除事件监听,避免内存泄漏。 示例代码(基于引用[3]修改): 假设我们有一个Timer成员变量this.timer677,我们在某个方法中启动它,然后在另一个方法中停止它。 创建并启动Timer: ```typescript public zuotimer() { // 创建Timer,间隔1毫秒,重复无限次(0表示无限) var timer6776: egret.Timer = new egret.Timer(1, 0); timer6776.addEventListener(egret.TimerEvent.TIMER, this.cont667, this); this.timer677 = timer6776; // 保存到成员变量 this.timer677.start(); // 启动 } ``` 停止Timer: ```typescript public stopTimer() { if (this.timer677) { this.timer677.stop(); // 停止计时器 // 可选:移除事件监听 this.timer677.removeEventListener(egret.TimerEvent.TIMER, this.cont667, this); } } ``` 注意:如果只是暂时停止,后面可能再次启动,那么可以只调用stop()而不移除事件监听。在不再需要Timer时(比如对象销毁时),一定要移除事件监听,避免内存泄漏。 另外,Timer停止后,可以使用reset()方法重置计时器(将当前计数重置为0),然后重新启动。 重置并重新启动: ```typescript public resetAndStartTimer() { if (this.timer677) { this.timer677.reset(); // 重置计数 this.timer677.start(); // 重新开始 } } ``` 总结: 停止Timer:调用stop()方法。 重置Timer:调用reset()方法(停止计时器并将计数重置为0)。 根据引用[2]中,Timer还有两个事件:TIMER和TIMER_COMPLETE。当计时器完成(即重复次数达到设定值)时会触发TIMER_COMPLETE事件,此时计时器会自动停止。如果手动停止,则不会触发TIMER_COMPLETE事件。 因此,在手动停止时,如果需要执行一些停止后的操作,应该在stop()之后直接调用,而不是依赖TIMER_COMPLETE事件。 完整示例: ```typescript class TimerExample extends egret.DisplayObjectContainer { private timer: egret.Timer; public constructor() { super(); this.timer = new egret.Timer(1000, 5); // 间隔1秒,重复5次 this.timer.addEventListener(egret.TimerEvent.TIMER, this.onTimer, this); this.timer.addEventListener(egret.TimerEvent.TIMER_COMPLETE, this.onTimerComplete, this); this.timer.start(); } private onTimer(): void { console.log("timer"); } private onTimerComplete(): void { console.log("timer complete"); } // 外部调用来手动停止 public stopMyTimer(): void { if (this.timer) { this.timer.stop(); // 注意:手动停止不会触发TIMER_COMPLETE事件 console.log("timer stopped manually"); } } // 在不需要的时候,移除监听 public dispose(): void { if (this.timer) { this.timer.removeEventListener(egret.TimerEvent.TIMER, this.onTimer, this); this.timer.removeEventListener(egret.TimerEvent.TIMER_COMPLETE, this.onTimerComplete, this); this.timer = null; } } } ``` 在上面的例子中,如果我们在计时器完成前调用了stopMyTimer(),那么就不会触发onTimerComplete方法。 所以,请根据实际需求选择停止方式,并做好清理工作。</think>### 如何停止 Egret Timer 在 Egret 中停止定时器有两种主要方法,以下是详细说明和示例代码: #### 方法 1:使用 `stop()` 方法(推荐) ```typescript // 创建定时器 private myTimer: egret.Timer; public createTimer() { // 创建每1秒执行一次的定时器(0表示无限重复) this.myTimer = new egret.Timer(1000, 0); this.myTimer.addEventListener(egret.TimerEvent.TIMER, this.timerHandler, this); this.myTimer.start(); } // 定时器回调函数 private timerHandler() { console.log("定时器执行中..."); } // 停止定时器 public stopTimer() { if (this.myTimer && this.myTimer.running) { this.myTimer.stop(); // 停止计时 this.myTimer.removeEventListener(egret.TimerEvent.TIMER, this.timerHandler, this); // 移除监听 console.log("定时器已停止"); } } ``` #### 方法 2:使用 `reset()` 方法 ```typescript // 停止并重置定时器 public resetTimer() { if (this.myTimer) { this.myTimer.reset(); // 重置并停止 this.myTimer.removeEventListener(egret.TimerEvent.TIMER, this.timerHandler, this); console.log("定时器已重置"); } } ``` #### 关键点说明: 1. **停止计时**: - `timer.stop()`:停止计时但保留当前计数状态 - `timer.reset()`:停止并重置计数为0 2. **内存管理**: ```typescript // 必须移除事件监听,避免内存泄漏 timer.removeEventListener(egret.TimerEvent.TIMER, callback); ``` 3. **状态检查**: ```typescript // 检查定时器是否在运行 if (timer.running) { timer.stop(); } ``` 4. **完整示例**: ```typescript class TimerManager { private timer: egret.Timer; private count: number = 0; constructor() { this.timer = new egret.Timer(1000, 0); // 1秒间隔,无限重复 this.timer.addEventListener(egret.TimerEvent.TIMER, this.onTimer, this); } public start() { this.timer.start(); } private onTimer() { this.count++; console.log(`计时: ${this.count}`); // 5秒后自动停止 if (this.count >= 5) { this.stop(); } } public stop() { if (this.timer.running) { this.timer.stop(); this.timer.removeEventListener(egret.TimerEvent.TIMER, this.onTimer, this); console.log("定时器已停止"); } } } // 使用示例 const timerManager = new TimerManager(); timerManager.start(); // 3秒后手动停止 egret.setTimeout(() => { timerManager.stop(); }, this, 3000); ``` ### 注意事项 1. **内存泄漏预防**: - 定时器使用后必须移除事件监听 - 页面销毁时停止所有定时器 2. **重复启动问题**: ```typescript // 避免重复启动 if (!this.timer.running) { this.timer.start(); } ``` 3. **性能优化**: - 不需要时及时停止后台定时器 - 避免创建过多短间隔定时器 4. **场景切换处理**: ```typescript // 场景关闭时停止定时器 public onClose() { this.stopAllTimers(); } ``` --- ### 相关问题 1. 如何实现 Egret 定时器的暂停/恢复功能? 2. Egret 中 `setTimeout` 和 `Timer` 的性能差异是什么? 3. 如何创建精确到毫秒的 Egret 定时器? 4. Egret 定时器在移动设备上的节流优化方法? 5. 如何检测并处理 Egret 中的定时器内存泄漏问题? [^1]: 定时器基本用法和事件监听 [^2]: `TIMER` 和 `TIMER_COMPLETE` 事件的区别 [^3]: 创建定时器的标准写法 [^4]: 定时器的创建和事件绑定 [^5]: 定时器其他功能的结合使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值