cocoscreator框架_CocosCreator通⽤框架设计——⽹络
编者按
本⽂来⾃于“Cocos 荣耀讲师”征稿活动第1期,最先发表于 Cocos 中⽂社区,作者,宝爷。宝爷是光⼦⼯作室的开发⼯程师,谦称⾃⼰为⼀枚码农,是⼀个热爱游戏、热爱开发、热爱学习并坚持沉淀知识的开发者,曾写过《精通 Cocos2d-x 游戏开发》基础卷与进阶卷,感谢宝爷为社区所做的贡献!
在 Cocos Creator 中发起⼀个 http 请求是⽐较简单的,但很多游戏希望能够和服务器之间保持长连接,以便服务端能够主动向客户端推送消息,⽽⾮总是由客户端发起请求,对于实时性要求较⾼的游戏更是如此。这⾥我们会设计⼀个通⽤的⽹络框架,可以⽅便地应⽤于我们的项⽬中。
项⽬源码
github/wyb10a10/cocos_creator_framework 本项⽬在不断完善中,包含 bug修改和代码更新,下⽂所展⽰的代码请以源码为准。
使⽤websocket
在实现这个⽹络框架之前,我们先了解⼀下 websocket。websocket 是⼀种基于 tcp 的全双⼯⽹络协议,
可以让⽹页创建持久性的连接,进⾏双向的通讯。在 Cocos Creator 中使⽤ websocket 既可以⽤于 H5 ⽹页游戏上,同样⽀持原⽣平台 Android 和 iOS。
构造 websocket 对象
在使⽤ websocket 时,第⼀步应该创建⼀个 websocket 对象。websocket 对象的构造函数可以传⼊2个参数,第⼀个是 url 字符串,第⼆个是协议字符串或字符串数组,指定了可接受的⼦协议,服务端需要选择其中的⼀个返回,才会建⽴连接,但我们⼀般⽤不到。
url 参数⾮常重要,主要分为4部分:协议、地址、端⼝、资源。
⽐如 ws://:
协议:必选项,默认是 ws 协议,如果需要安全加密则使⽤ wss。
地址:必选项,可以是 ip 或域名,当然建议使⽤域名。
端⼝:可选项,在不指定的情况下,ws 的默认端⼝为 80,wss 的默认端⼝为 443。
资源:可选性,⼀般是跟在域名后某资源路径,我们基本不需要它。
websocket 的状态
websocket 有4个状态,可以通过 readyState 属性查询:
0 CONNECTING 尚未建⽴连接。
1 OPEN WebSocket连接已建⽴,可以进⾏通信。感动中国10大人物
2 CLOSING 连接正在进⾏关闭握⼿,或者该close()⽅法已被调⽤。
3 CLOSED 连接已关闭。
websocket 的 API
websocket 只有2个 API,void send( data ) 发送数据和 void close( code, reason ) 关闭连接。
send ⽅法只接收⼀个参数——即要发送的数据,类型可以是以下4个类型的任意⼀种:string | ArrayBufferLike | Blob | ArrayBufferView。
如果要发送的数据是⼆进制,我们可以通过 websocket 对象的 binaryType 属性来指定⼆进制的类型,binaryType 只可以被设置为“blob”或“arraybuffer”,默认为“blob”。如果我们要传输的是⽂件这样
较为固定的、⽤于写⼊到磁盘的数据,使⽤ blob。
⽽你希望传输的对象在内存中进⾏处理则使⽤较为灵活的 arraybuffer。如果要从其他⾮ blob 对象和数据构造⼀个 blob,需要使⽤blob 的构造函数。
在发送数据时,官⽅有2个建议:
检测 websocket 对象的 readyState 是否为 OPEN,是才进⾏ send。
检测 websocket 对象的 bufferedAmount 是否为0,是才进⾏ send(为了避免消息堆积,该属性表⽰调⽤ send 后堆积在
websocket 缓冲区的还未真正发送出去的数据长度)。
close ⽅法接收2个可选的参数,code 表⽰错误码,我们应该传⼊ 1000 或 3000~4999 之间的整数,reason 可以⽤于表⽰关闭的原因,长度不可超过 123 字节。
websocket 的回调
websocket 提供了4个回调函数供我们绑定:
onopen:连接成功后调⽤。
985高校onmessage:有消息过来时调⽤:传⼊的对象有 data 属性,可能是字符串、blob 或 arraybuffer。
onerror:出现⽹络错误时调⽤:传⼊的对象有 data 属性,通常是错误描述的字符串。
onclose:连接关闭时调⽤:传⼊的对象有 code、reason、wasClean 等属性。
注意:当⽹络出错时,会先调⽤ onerror 再调⽤ onclose,⽆论何种原因的连接关闭,onclose 都会被调⽤。
Echo 实例
默认的 url 前缀是wss,由于 wss 抽风,使⽤ ws 才可以连接上,如果 ws 也抽风,可以试试连这个地址
ws://121.40.165.18:8800,这是国内的⼀个免费测试 websocket 的⽹址。
祖先的摇篮参考链接
设计框架
⼀个通⽤的⽹络框架,在通⽤的前提下还需要能够⽀持各种项⽬的差异需求,根据经验,常见的需求差异如下:⽤户协议差异,游戏可能传输 json、protobuf、flatbuffer 或者⾃定义的⼆进制协议。
底层协议差异,我们可能使⽤ websocket、或者⼩游戏的 wx.websocket、甚⾄在原⽣平台我们希望使⽤ tcp/udp/kcp 等协议。
登陆认证流程,在使⽤长连接之前我们理应进⾏登陆认证,⽽不同游戏登陆认证的⽅式不同。
⽹络异常处理,⽐如超时时间是多久,超时后的表现是怎样的,请求时是否应该屏蔽 UI 等待服务器响应,⽹络断开后表现如何,⾃动重连还是由玩家点击重连按钮进⾏重连,重连之后是否重发断⽹期间的消息?等等这些。
多连接的处理,某些游戏可能需要⽀持多个不同的连接,⼀般不会超过2个,⽐如⼀个主连接负责处理⼤厅等业务消息,⼀个战⽃连接直接连战⽃服务器,或者连接聊天服务器。
根据上⾯的这些需求,我们对功能模块进⾏拆分,尽量保证模块的⾼内聚,低耦合。
ProtocolHelper 协议处理模块——当我们拿到⼀块 buffer时,我们可能需要知道这个 buffer 对应的协议或者 id 是多少,⽐如我们在请
求的时候就传⼊了响应的处理回调,那么常⽤的做法可能会⽤⼀个⾃增的 id 来区别每⼀个请求,或者是⽤协议号来区分不同的请求,这些
是开发者需要实现的。我们还需要从 buffer 中获取包的长度是多少?包长的合理范围是多少?⼼跳包长什么样⼦等等。
Socket 模块——实现最基础的通讯功能,⾸先定义 Socket 的接⼝类 ISocket,定义如连接、关闭、数据接收与发送等接⼝,然后⼦类继
承并实现这些接⼝。
NetworkTips ⽹络显⽰模块——实现如连接中、重连中、加载中、⽹络断开等状态的显⽰,以及 UI 的屏蔽。
NetNode ⽹络节点——所谓⽹络节点,其实主要的职责是将上⾯的功能串联起来,为⽤户提供⼀个易⽤的接⼝。
NetManager 管理⽹络节点的单例——我们可能有多个⽹络节点(多条连接),所以这⾥使⽤单例来进⾏管理,使⽤单例来操作⽹络节点也会
更加⽅便。
ProtocolHelper
在这⾥定义了⼀个 IProtocolHelper 的简单接⼝,如下所⽰:
export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView);// 协议辅助接⼝export interface IProtocolHelper { getHeadlen(): number; // 返
Socket
在这⾥定义了⼀个 ISocket 的简单接⼝,如下所⽰:
// Socket接⼝export interface ISocket { onConnected: (event) => void; // 连接回调 onMessage: (msg: NetData) => void; // 消息回调 onError: (event)
接下来我们实现⼀个 WebSock,继承于 ISocket,我们只需要实现 connect、send 和 close 接⼝即可。send 和 close 都是对
websocket 对简单封装,connect 则需要根据传⼊的 ip、端⼝等参数构造⼀个 url 来创建 websocket,并绑定 websocket 的回调。
export class WebSock implements ISocket { private _ws: WebSocket = null; // websocket对象 onConnected: (event) => void = null; onMessage: (ms
NetworkTips
INetworkTips 提供了⾮常的接⼝,重连和请求的开关,框架会在合适的时机调⽤它们,我们可以继承 INetworkTips 并定制我们的⽹络相
关提⽰信息,需要注意的是这些接⼝可能会被**多次调⽤**。
非主流游戏名字// ⽹络提⽰接⼝export interface INetworkTips { connectTips(isShow: boolean): void; reconnectTips(isShow: boolean): void; requestTips(isShow: boolean): vo
NetNode
NetNode 是整个⽹络框架中最为关键的部分,⼀个 NetNode 实例表⽰⼀个完整的连接对象,基于 NetNode 我们可以⽅便地进⾏扩展,
它的主要职责有:
连接维护
连接的建⽴与鉴权(是否鉴权、如何鉴权由⽤户的回调决定)
断线重连后的数据重发处理
⼼跳机制确保连接有效(⼼跳包间隔由配置,⼼跳包的内容由ProtocolHelper定义)
连接的关闭
数据发送
⽀持断线重传,超时重传
⽀持唯⼀发送(避免同⼀时间重复发送)
数据接收
⽀持持续监听
⽀持request-respone模式
界⾯展⽰教师节的由来50字
可⾃定义⽹络延迟、短线重连等状态的表现
⾸先我们定义了 NetTipsType、NetNodeState 两个枚举,以及 NetConnectOptions 结构供 NetNode 使⽤。
接下来是 NetNode 的成员变量,NetNode 的变量可以分为以下⼏类:
NetNode ⾃⾝的状态变量,如 ISocket 对象、当前状态、连接参数等等。
各种回调,包括连接、断开连接、协议处理、⽹络提⽰等回调。
各种定时器,如⼼跳、重连相关的定时器。
请求列表与监听列表,都是⽤于接收到的消息处理。
接下来介绍⽹络相关的成员函数,⾸先看初始化与:
init ⽅法⽤于初始化 NetNode,主要是指定 Socket 与协议等处理对象。
connect ⽅法⽤于连接服务器。
initSocket ⽅法⽤于绑定 Socket 的回调到 NetNode 中。
updateNetTips ⽅法⽤于刷新⽹络提⽰。
onConnected ⽅法在⽹络连接成功后调⽤,⾃动进⼊鉴权流程(如果设置了_connectedCallback),在鉴权完成后需要调⽤ onChecked
⽅法使 NetNode 进⼊可通讯的状态,在未鉴权的情况,我们不应该发送任何业务请求,但登录验证这类请求应该发送给服务器,这类请求
可以通过带force参数强制发送给服务器。
营业执照网上办理流程接收到任何消息都会触发 onMessage,⾸先会对数据包进⾏校验,校验的规则可以在⾃⼰的 ProtocolHelper 中实现,如果是⼀个合法的
数据包,我们会将⼼跳和超时计时器进⾏更新——重新计时,最后在 _requests 和 _listener 中到该消息的处理函数,这⾥是通过
rspCmd 进⾏查的,rspCmd 是从 ProtocolHelper 的 getPackageId 取出的,我们可以将协议的命令或者序号返回,由我们⾃⼰来决
定请求和响应如何对应。
onError 和 onClosed 是⽹络出错和关闭时调⽤的,⽆论是否出错,最终都会调⽤ onClosed,在这⾥我们执⾏断线回调,以及做⾃动重
连的处理。当然也可以调⽤ close来关闭套接字。close 与 closeSocket 的区别在于 closeSocket 只是关闭套接字——我仍然要使⽤当前
的 NetNode,可能通过下⼀次 connect 恢复⽹络。⽽ close则是清除所有的状态。
发起⽹络请求有3种⽅式:
send ⽅法,纯粹地发送数据,如果当前断⽹或者验证中会进⼊ _request 队列。
request ⽅法,在请求的时候即以闭包的⽅式传⼊回调,在该请求的响应回到时会执⾏回调,如果同时有多个相同的请求,那么这 N 个请
求的响应会依次回到客户端,响应回调也会依次执⾏(每次只会执⾏⼀个回调)。
requestUnique ⽅法,如果我们不希望有多个相同的请求,可以使⽤ requestUnique 来确保每⼀种请求同时只会有⼀个。
这⾥确保没有重复之所以使⽤的是遍历 _requests,是因为我们不会积压⼤量的请求到 _requests中,超时或异常重发也不会导致
_requests 的积压,因为重发的逻辑是由 NetNode 控制的,⽽且在⽹络断开的情况下,我们理应屏蔽⽤户发起请求,此时⼀般会有
⼀个全屏遮罩——⽹络出现波动之类的提⽰。
我们有2种回调,⼀种是前⾯的 request 回调,这种回调是临时性的,⼀般随着请求-响应-执⾏⽽⽴即清理,_listener 回调则是常驻的,需
要我们⼿动管理的,⽐如打开某界⾯时监听、离开是关闭,或者在游戏⼀开始就进⾏监听。适合处理服务器的主动推送消息。
最后是⼼跳与超时相关的定时器,我们每隔 _heartTime 会发送⼀个⼼跳包,每隔 _receiveTime 检测如果没有收到服务器返回的包,则判
断⽹络断开。
完整代码,⼤家可以进⼊源码查看!
NetManager
NetManager ⽤于管理 NetNode,这是由于我们可能需要⽀持多个不同的连接对象,所以需要⼀个 NetManager 专门来管理 NetNode,
同时,NetManager 作为⼀个单例,也可以⽅便我们调⽤⽹络。
export class NetManager { private static _instance: NetManager = null; protected _channels: { [key: number]: NetNode } = {}; public static getInstance(): Net
测试例⼦
接下来我们⽤⼀个简单的例⼦来演⽰⼀下⽹络框架的基本使⽤,⾸先我们需要拼⼀个简单的界⾯⽤于展⽰,3个按钮(连接、发送、关闭),2
个输⼊框(输⼊ url、输⼊要发送的内容),⼀个⽂本框(显⽰从服务器接收到的数据),如下图所⽰。
该例⼦连接的是 websocket 官⽅的 地址,这个服务器会将我们发送给它的所有消息都原样返回给我们。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论