CocosCreator通用框架設(shè)計(jì)之網(wǎng)絡(luò)
前言
在 Cocos Creator 中發(fā)起一個(gè) http 請(qǐng)求是比較簡(jiǎn)單的,但很多游戲希望能夠和服務(wù)器之間保持長(zhǎng)連接,以便服務(wù)端能夠主動(dòng)向客戶端推送消息,而非總是由客戶端發(fā)起請(qǐng)求,對(duì)于實(shí)時(shí)性要求較高的游戲更是如此。這里我們會(huì)設(shè)計(jì)一個(gè)通用的網(wǎng)絡(luò)框架,可以方便地應(yīng)用于我們的項(xiàng)目中。
使用websocket
在實(shí)現(xiàn)這個(gè)網(wǎng)絡(luò)框架之前,我們先了解一下 websocket。websocket 是一種基于 tcp 的全雙工網(wǎng)絡(luò)協(xié)議,可以讓網(wǎng)頁(yè)創(chuàng)建持久性的連接,進(jìn)行雙向的通訊。在 Cocos Creator 中使用 websocket 既可以用于 H5 網(wǎng)頁(yè)游戲上,同樣支持原生平臺(tái) Android 和 iOS。
構(gòu)造 websocket 對(duì)象
在使用 websocket 時(shí),第一步應(yīng)該創(chuàng)建一個(gè) websocket 對(duì)象。websocket 對(duì)象的構(gòu)造函數(shù)可以傳入2個(gè)參數(shù),第一個(gè)是 url 字符串,第二個(gè)是協(xié)議字符串或字符串?dāng)?shù)組,指定了可接受的子協(xié)議,服務(wù)端需要選擇其中的一個(gè)返回,才會(huì)建立連接,但我們一般用不到。
url 參數(shù)非常重要,主要分為4部分:協(xié)議、地址、端口、資源。
比如 ws://echo.websocket.org:
- 協(xié)議:必選項(xiàng),默認(rèn)是 ws 協(xié)議,如果需要安全加密則使用 wss。
- 地址:必選項(xiàng),可以是 ip 或域名,當(dāng)然建議使用域名。
- 端口:可選項(xiàng),在不指定的情況下,ws 的默認(rèn)端口為 80,wss 的默認(rèn)端口為 443。
- 資源:可選性,一般是跟在域名后某資源路徑,我們基本不需要它。
websocket 的狀態(tài)
websocket 有4個(gè)狀態(tài),可以通過(guò) readyState 屬性查詢:
- 0 CONNECTING 尚未建立連接。
- 1 OPEN WebSocket連接已建立,可以進(jìn)行通信。
- 2 CLOSING 連接正在進(jìn)行關(guān)閉握手,或者該close()方法已被調(diào)用。
- 3 CLOSED 連接已關(guān)閉。
websocket 的 API
websocket 只有2個(gè) API,void send( data ) 發(fā)送數(shù)據(jù)和 void close( code, reason ) 關(guān)閉連接。
send 方法只接收一個(gè)參數(shù)——即要發(fā)送的數(shù)據(jù),類型可以是以下4個(gè)類型的任意一種:string | ArrayBufferLike | Blob | ArrayBufferView。
如果要發(fā)送的數(shù)據(jù)是二進(jìn)制,我們可以通過(guò) websocket 對(duì)象的 binaryType 屬性來(lái)指定二進(jìn)制的類型,binaryType 只可以被設(shè)置為“blob”或“arraybuffer”,默認(rèn)為“blob”。如果我們要傳輸?shù)氖俏募@樣較為固定的、用于寫入到磁盤的數(shù)據(jù),使用 blob。而你希望傳輸?shù)膶?duì)象在內(nèi)存中進(jìn)行處理則使用較為靈活的 arraybuffer。如果要從其他非 blob 對(duì)象和數(shù)據(jù)構(gòu)造一個(gè) blob,需要使用 blob 的構(gòu)造函數(shù)。
在發(fā)送數(shù)據(jù)時(shí),官方有2個(gè)建議:
- 檢測(cè) websocket 對(duì)象的 readyState 是否為 OPEN,是才進(jìn)行 send。
- 檢測(cè) websocket 對(duì)象的 bufferedAmount 是否為0,是才進(jìn)行 send(為了避免消息堆積,該屬性表示調(diào)用 send 后堆積在 websocket 緩沖區(qū)的還未真正發(fā)送出去的數(shù)據(jù)長(zhǎng)度)。
close 方法接收2個(gè)可選的參數(shù),code 表示錯(cuò)誤碼,我們應(yīng)該傳入 1000 或 3000~4999 之間的整數(shù),reason 可以用于表示關(guān)閉的原因,長(zhǎng)度不可超過(guò) 123 字節(jié)。
websocket 的回調(diào)
websocket 提供了4個(gè)回調(diào)函數(shù)供我們綁定:
- onopen:連接成功后調(diào)用。
- onmessage:有消息過(guò)來(lái)時(shí)調(diào)用:傳入的對(duì)象有 data 屬性,可能是字符串、blob 或 arraybuffer。
- onerror:出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤時(shí)調(diào)用:傳入的對(duì)象有 data 屬性,通常是錯(cuò)誤描述的字符串。
- onclose:連接關(guān)閉時(shí)調(diào)用:傳入的對(duì)象有 code、reason、wasClean 等屬性。
注意:當(dāng)網(wǎng)絡(luò)出錯(cuò)時(shí),會(huì)先調(diào)用 onerror 再調(diào)用 onclose,無(wú)論何種原因的連接關(guān)閉,onclose 都會(huì)被調(diào)用。
Echo 實(shí)例
下面 websocket 官網(wǎng)的 echo demo 的代碼,可以將其寫入一個(gè) html 文件中并用瀏覽器打開,打開后會(huì)自動(dòng)創(chuàng)建 websocket 連接,在連接上時(shí)主動(dòng)發(fā)送了一條消息“WebSocket rocks”,服務(wù)器會(huì)將該消息返回,觸發(fā) onMessage,將信息打印到屏幕上,然后關(guān)閉連接。具體可以參考:http://www.websocket.org/echo.html17
默認(rèn)的 url 前綴是wss,由于 wss 抽風(fēng),使用 ws 才可以連接上,如果 ws 也抽風(fēng),可以試試連這個(gè)地址ws://121.40.165.18:8800,這是國(guó)內(nèi)的一個(gè)免費(fèi)測(cè)試 websocket 的網(wǎng)址。
設(shè)計(jì)框架
一個(gè)通用的網(wǎng)絡(luò)框架,在通用的前提下還需要能夠支持各種項(xiàng)目的差異需求,根據(jù)經(jīng)驗(yàn),常見的需求差異如下:
- 用戶協(xié)議差異,游戲可能傳輸 json、protobuf、flatbuffer 或者自定義的二進(jìn)制協(xié)議。
- 底層協(xié)議差異,我們可能使用 websocket、或者微信小游戲的 wx.websocket、甚至在原生平臺(tái)我們希望使用 tcp/udp/kcp 等協(xié)議。
- 登陸認(rèn)證流程,在使用長(zhǎng)連接之前我們理應(yīng)進(jìn)行登陸認(rèn)證,而不同游戲登陸認(rèn)證的方式不同。
- 網(wǎng)絡(luò)異常處理,比如超時(shí)時(shí)間是多久,超時(shí)后的表現(xiàn)是怎樣的,請(qǐng)求時(shí)是否應(yīng)該屏蔽 UI 等待服務(wù)器響應(yīng),網(wǎng)絡(luò)斷開后表現(xiàn)如何,自動(dòng)重連還是由玩家點(diǎn)擊重連按鈕進(jìn)行重連,重連之后是否重發(fā)斷網(wǎng)期間的消息?等等這些。
- 多連接的處理,某些游戲可能需要支持多個(gè)不同的連接,一般不會(huì)超過(guò)2個(gè),比如一個(gè)主連接負(fù)責(zé)處理大廳等業(yè)務(wù)消息,一個(gè)戰(zhàn)斗連接直接連戰(zhàn)斗服務(wù)器,或者連接聊天服務(wù)器。
根據(jù)上面的這些需求,我們對(duì)功能模塊進(jìn)行拆分,盡量保證模塊的高內(nèi)聚,低耦合。
ProtocolHelper 協(xié)議處理模塊——當(dāng)我們拿到一塊 buffer時(shí),我們可能需要知道這個(gè) buffer 對(duì)應(yīng)的協(xié)議或者 id 是多少,比如我們?cè)谡?qǐng)求的時(shí)候就傳入了響應(yīng)的處理回調(diào),那么常用的做法可能會(huì)用一個(gè)自增的 id 來(lái)區(qū)別每一個(gè)請(qǐng)求,或者是用協(xié)議號(hào)來(lái)區(qū)分不同的請(qǐng)求,這些是開發(fā)者需要實(shí)現(xiàn)的。我們還需要從 buffer 中獲取包的長(zhǎng)度是多少?包長(zhǎng)的合理范圍是多少?心跳包長(zhǎng)什么樣子等等。
Socket 模塊——實(shí)現(xiàn)最基礎(chǔ)的通訊功能,首先定義 Socket 的接口類 ISocket,定義如連接、關(guān)閉、數(shù)據(jù)接收與發(fā)送等接口,然后子類繼承并實(shí)現(xiàn)這些接口。
NetworkTips 網(wǎng)絡(luò)顯示模塊——實(shí)現(xiàn)如連接中、重連中、加載中、網(wǎng)絡(luò)斷開等狀態(tài)的顯示,以及 UI 的屏蔽。
NetNode 網(wǎng)絡(luò)節(jié)點(diǎn)——所謂網(wǎng)絡(luò)節(jié)點(diǎn),其實(shí)主要的職責(zé)是將上面的功能串聯(lián)起來(lái),為用戶提供一個(gè)易用的接口。
NetManager 管理網(wǎng)絡(luò)節(jié)點(diǎn)的單例——我們可能有多個(gè)網(wǎng)絡(luò)節(jié)點(diǎn)(多條連接),所以這里使用單例來(lái)進(jìn)行管理,使用單例來(lái)操作網(wǎng)絡(luò)節(jié)點(diǎn)也會(huì)更加方便。
ProtocolHelper
在這里定義了一個(gè) IProtocolHelper 的簡(jiǎn)單接口,如下所示:
export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView);// 協(xié)議輔助接口 export interface IProtocolHelper { getHeadlen(): number; // 返回包頭長(zhǎng)度 getHearbeat(): NetData; // 返回一個(gè)心跳包 getPackageLen(msg: NetData): number; // 返回整個(gè)包的長(zhǎng)度 checkPackage(msg: NetData): boolean; // 檢查包數(shù)據(jù)是否合法 getPackageId(msg: NetData): number; // 返回包的id或協(xié)議類型 }
Socket
在這里定義了一個(gè) ISocket 的簡(jiǎn)單接口,如下所示:
// Socket接口 export interface ISocket { onConnected: (event) => void; //連接回調(diào) onMessage: (msg: NetData) => void; // 消息回調(diào) onError: (event) => void; // 錯(cuò)誤回調(diào) onClosed: (event) => void; // 關(guān)閉回調(diào) connect(ip: string, port: number); // 連接接口 send(buffer: NetData); // 數(shù)據(jù)發(fā)送接口 close(code?: number, reason?: string); // 關(guān)閉接口 }
接下來(lái)我們實(shí)現(xiàn)一個(gè) WebSock,繼承于 ISocket,我們只需要實(shí)現(xiàn) connect、send 和 close 接口即可。send 和 close 都是對(duì) websocket 對(duì)簡(jiǎn)單封裝,connect 則需要根據(jù)傳入的 ip、端口等參數(shù)構(gòu)造一個(gè) url 來(lái)創(chuàng)建 websocket,并綁定 websocket 的回調(diào)。
export class WebSock implements ISocket { private _ws: WebSocket = null; // websocket對(duì)象 onConnected: (event) => void = null; onMessage: (msg) => void = null; onError: (event) => void = null; onClosed: (event) => void = null; connect(options: any) { if (this._ws) { if (this._ws.readyState === WebSocket.CONNECTING) { console.log("websocket connecting, wait for a moment...") return false; } } let url = null; if(options.url) { url = options.url; } else { let ip = options.ip; let port = options.port; let protocol = options.protocol; url = `${protocol}://${ip}:${port}`; } this._ws = new WebSocket(url); this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer"; this._ws.onmessage = (event) => { this.onMessage(event.data); }; this._ws.onopen = this.onConnected; this._ws.onerror = this.onError; this._ws.onclose = this.onClosed; return true; } send(buffer: NetData) { if (this._ws.readyState == WebSocket.OPEN) { this._ws.send(buffer); return true; } return false; } close(code?: number, reason?: string) { this._ws.close(); } }
NetworkTips
INetworkTips 提供了非常的接口,重連和請(qǐng)求的開關(guān),框架會(huì)在合適的時(shí)機(jī)調(diào)用它們,我們可以繼承 INetworkTips 并定制我們的網(wǎng)絡(luò)相關(guān)提示信息,需要注意的是這些接口可能會(huì)被**多次調(diào)用**。
// 網(wǎng)絡(luò)提示接口 export interface INetworkTips { connectTips(isShow: boolean): void; reconnectTips(isShow: boolean): void; requestTips(isShow: boolean): void; }
NetNode
NetNode 是整個(gè)網(wǎng)絡(luò)框架中最為關(guān)鍵的部分,一個(gè) NetNode 實(shí)例表示一個(gè)完整的連接對(duì)象,基于 NetNode 我們可以方便地進(jìn)行擴(kuò)展,它的主要職責(zé)有:
連接維護(hù)
- 連接的建立與鑒權(quán)(是否鑒權(quán)、如何鑒權(quán)由用戶的回調(diào)決定)
- 斷線重連后的數(shù)據(jù)重發(fā)處理
- 心跳機(jī)制確保連接有效(心跳包間隔由配置,心跳包的內(nèi)容由ProtocolHelper定義)
- 連接的關(guān)閉
數(shù)據(jù)發(fā)送
- 支持?jǐn)嗑€重傳,超時(shí)重傳
- 支持唯一發(fā)送(避免同一時(shí)間重復(fù)發(fā)送)
數(shù)據(jù)接收
- 支持持續(xù)監(jiān)聽
- 支持request-respone模式
界面展示
- 可自定義網(wǎng)絡(luò)延遲、短線重連等狀態(tài)的表現(xiàn)
- 首先我們定義了 NetTipsType、NetNodeState 兩個(gè)枚舉,以及 NetConnectOptions 結(jié)構(gòu)供 NetNode 使用。
- 接下來(lái)是 NetNode 的成員變量,NetNode 的變量可以分為以下幾類:
- NetNode 自身的狀態(tài)變量,如 ISocket 對(duì)象、當(dāng)前狀態(tài)、連接參數(shù)等等。
- 各種回調(diào),包括連接、斷開連接、協(xié)議處理、網(wǎng)絡(luò)提示等回調(diào)。
- 各種定時(shí)器,如心跳、重連相關(guān)的定時(shí)器。
- 請(qǐng)求列表與監(jiān)聽列表,都是用于接收到的消息處理。
接下來(lái)介紹網(wǎng)絡(luò)相關(guān)的成員函數(shù),首先看初始化與:
- init 方法用于初始化 NetNode,主要是指定 Socket 與協(xié)議等處理對(duì)象。
- connect 方法用于連接服務(wù)器。
- initSocket 方法用于綁定 Socket 的回調(diào)到 NetNode 中。
- updateNetTips 方法用于刷新網(wǎng)絡(luò)提示。
onConnected 方法在網(wǎng)絡(luò)連接成功后調(diào)用,自動(dòng)進(jìn)入鑒權(quán)流程(如果設(shè)置了_connectedCallback),在鑒權(quán)完成后需要調(diào)用 onChecked 方法使 NetNode 進(jìn)入可通訊的狀態(tài),在未鑒權(quán)的情況,我們不應(yīng)該發(fā)送任何業(yè)務(wù)請(qǐng)求,但登錄驗(yàn)證這類請(qǐng)求應(yīng)該發(fā)送給服務(wù)器,這類請(qǐng)求可以通過(guò)帶force參數(shù)強(qiáng)制發(fā)送給服務(wù)器。
接收到任何消息都會(huì)觸發(fā) onMessage,首先會(huì)對(duì)數(shù)據(jù)包進(jìn)行校驗(yàn),校驗(yàn)的規(guī)則可以在自己的 ProtocolHelper 中實(shí)現(xiàn),如果是一個(gè)合法的數(shù)據(jù)包,我們會(huì)將心跳和超時(shí)計(jì)時(shí)器進(jìn)行更新——重新計(jì)時(shí),最后在 _requests 和 _listener 中找到該消息的處理函數(shù),這里是通過(guò) rspCmd 進(jìn)行查找的,rspCmd 是從 ProtocolHelper 的 getPackageId 取出的,我們可以將協(xié)議的命令或者序號(hào)返回,由我們自己來(lái)決定請(qǐng)求和響應(yīng)如何對(duì)應(yīng)。
onError 和 onClosed 是網(wǎng)絡(luò)出錯(cuò)和關(guān)閉時(shí)調(diào)用的,無(wú)論是否出錯(cuò),最終都會(huì)調(diào)用 onClosed,在這里我們執(zhí)行斷線回調(diào),以及做自動(dòng)重連的處理。當(dāng)然也可以調(diào)用 close來(lái)關(guān)閉套接字。close 與 closeSocket 的區(qū)別在于 closeSocket 只是關(guān)閉套接字——我仍然要使用當(dāng)前的 NetNode,可能通過(guò)下一次 connect 恢復(fù)網(wǎng)絡(luò)。而 close則是清除所有的狀態(tài)。
發(fā)起網(wǎng)絡(luò)請(qǐng)求有3種方式:
send 方法,純粹地發(fā)送數(shù)據(jù),如果當(dāng)前斷網(wǎng)或者驗(yàn)證中會(huì)進(jìn)入 _request 隊(duì)列。
request 方法,在請(qǐng)求的時(shí)候即以閉包的方式傳入回調(diào),在該請(qǐng)求的響應(yīng)回到時(shí)會(huì)執(zhí)行回調(diào),如果同時(shí)有多個(gè)相同的請(qǐng)求,那么這 N 個(gè)請(qǐng)求的響應(yīng)會(huì)依次回到客戶端,響應(yīng)回調(diào)也會(huì)依次執(zhí)行(每次只會(huì)執(zhí)行一個(gè)回調(diào))。
requestUnique 方法,如果我們不希望有多個(gè)相同的請(qǐng)求,可以使用 requestUnique 來(lái)確保每一種請(qǐng)求同時(shí)只會(huì)有一個(gè)。
這里確保沒(méi)有重復(fù)之所以使用的是遍歷 _requests,是因?yàn)槲覀儾粫?huì)積壓大量的請(qǐng)求到 _requests中,超時(shí)或異常重發(fā)也不會(huì)導(dǎo)致 _requests 的積壓,因?yàn)橹匕l(fā)的邏輯是由 NetNode 控制的,而且在網(wǎng)絡(luò)斷開的情況下,我們理應(yīng)屏蔽用戶發(fā)起請(qǐng)求,此時(shí)一般會(huì)有一個(gè)全屏遮罩——網(wǎng)絡(luò)出現(xiàn)波動(dòng)之類的提示。
我們有2種回調(diào),一種是前面的 request 回調(diào),這種回調(diào)是臨時(shí)性的,一般隨著請(qǐng)求-響應(yīng)-執(zhí)行而立即清理,_listener 回調(diào)則是常駐的,需要我們手動(dòng)管理的,比如打開某界面時(shí)監(jiān)聽、離開是關(guān)閉,或者在游戲一開始就進(jìn)行監(jiān)聽。適合處理服務(wù)器的主動(dòng)推送消息。
最后是心跳與超時(shí)相關(guān)的定時(shí)器,我們每隔 _heartTime 會(huì)發(fā)送一個(gè)心跳包,每隔 _receiveTime 檢測(cè)如果沒(méi)有收到服務(wù)器返回的包,則判斷網(wǎng)絡(luò)斷開。
完整代碼,大家可以進(jìn)入源碼查看!
NetManager
NetManager 用于管理 NetNode,這是由于我們可能需要支持多個(gè)不同的連接對(duì)象,所以需要一個(gè) NetManager 專門來(lái)管理 NetNode,同時(shí),NetManager 作為一個(gè)單例,也可以方便我們調(diào)用網(wǎng)絡(luò)。
export class NetManager { private static _instance: NetManager = null; protected _channels: { [key: number]: NetNode } = {}; public static getInstance(): NetManager { if (this._instance == null) { this._instance = new NetManager(); } return this._instance; } // 添加Node,返回ChannelID public setNetNode(newNode: NetNode, channelId: number = 0) { this._channels[channelId] = newNode; } // 移除Node public removeNetNode(channelId: number) { delete this._channels[channelId]; } // 調(diào)用Node連接 public connect(options: NetConnectOptions, channelId: number = 0): boolean { if (this._channels[channelId]) { return this._channels[channelId].connect(options); } return false; } // 調(diào)用Node發(fā)送 public send(buf: NetData, force: boolean = false, channelId: number = 0): boolean { let node = this._channels[channelId]; if (node) { return node.send(buf, force); } return false; } // 發(fā)起請(qǐng)求,并在在結(jié)果返回時(shí)調(diào)用指定好的回調(diào)函數(shù) public request(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0) { let node = this._channels[channelId]; if (node) { node.request(buf, rspCmd, rspObject, showTips, force); } } // 同request,但在request之前會(huì)先判斷隊(duì)列中是否已有rspCmd,如有重復(fù)的則直接返回 public requestUnique(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0): boolean { let node = this._channels[channelId]; if (node) { return node.requestUnique(buf, rspCmd, rspObject, showTips, force); } return false; } // 調(diào)用Node關(guān)閉 public close(code ? : number, reason ? : string, channelId: number = 0) { if (this._channels[channelId]) { return this._channels[channelId].closeSocket(code, reason); } }
測(cè)試?yán)?/strong>
接下來(lái)我們用一個(gè)簡(jiǎn)單的例子來(lái)演示一下網(wǎng)絡(luò)框架的基本使用,首先我們需要拼一個(gè)簡(jiǎn)單的界面用于展示,3個(gè)按鈕(連接、發(fā)送、關(guān)閉),2個(gè)輸入框(輸入 url、輸入要發(fā)送的內(nèi)容),一個(gè)文本框(顯示從服務(wù)器接收到的數(shù)據(jù)),如下圖所示。
該例子連接的是 websocket 官方的 echo.websocket.org 地址,這個(gè)服務(wù)器會(huì)將我們發(fā)送給它的所有消息都原樣返回給我們。
接下來(lái),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Component,這里新建了一個(gè) NetExample.ts 文件,做的事情非常簡(jiǎn)單,在初始化的時(shí)候創(chuàng)建 NetNode、綁定默認(rèn)接收回調(diào),在接收回調(diào)中將服務(wù)器返回的文本顯示到 msgLabel中。接著是連接、發(fā)送和關(guān)閉幾個(gè)接口的實(shí)現(xiàn):
// 不關(guān)鍵的代碼省略 @ccclassexport default class NetExample extends cc.Component { @property(cc.Label) textLabel: cc.Label = null; @property(cc.Label) urlLabel: cc.Label = null; @property(cc.RichText) msgLabel: cc.RichText = null; private lineCount: number = 0; onLoad() { let Node = new NetNode(); Node.init(new WebSock(), new DefStringProtocol()); Node.setResponeHandler(0, (cmd: number, data: NetData) => { if (this.lineCount > 5) { let idx = this.msgLabel.string.search("\n"); this.msgLabel.string = this.msgLabel.string.substr(idx + 1); } this.msgLabel.string += `${data}\n`; ++this.lineCount; }); NetManager.getInstance().setNetNode(Node); } onConnectClick() { NetManager.getInstance().connect({ url: this.urlLabel.string }); } onSendClick() { NetManager.getInstance().send(this.textLabel.string); } onDisconnectClick() { NetManager.getInstance().close(); } }
代碼完成后,將其掛載到場(chǎng)景的 Canvas 節(jié)點(diǎn)下(其他節(jié)點(diǎn)也可以),然后將場(chǎng)景中的 Label 和 RichText 拖拽到我們的 NetExample 的屬性面板中:
運(yùn)行效果如下所示:
小結(jié)
可以看到,Websocket 的使用很簡(jiǎn)單,我們?cè)陂_發(fā)的過(guò)程中會(huì)碰到各種各樣的需求和問(wèn)題,要實(shí)現(xiàn)一個(gè)好的設(shè)計(jì),快速地解決問(wèn)題。
我們一方面需要對(duì)我們使用的技術(shù)本身有深入的理解,websocket 的底層協(xié)議傳輸是如何實(shí)現(xiàn)的?與 tcp、http 的區(qū)別在哪里?基于 websocket 能否使用 udp 進(jìn)行傳輸呢?使用 websocket 發(fā)送數(shù)據(jù)是否需要自己對(duì)數(shù)據(jù)流進(jìn)行分包(websocket 協(xié)議保證了包的完整)?數(shù)據(jù)的發(fā)送是否出現(xiàn)了發(fā)送緩存的堆積(查看 bufferedAmount)?
另外需要對(duì)我們的使用場(chǎng)景及需求本身的理解,對(duì)需求的理解越透徹,越能做出好的設(shè)計(jì)。哪些需求是項(xiàng)目相關(guān)的,哪些需求是通用的?通用的需求是必須的還是可選的?不同的變化我們應(yīng)該封裝成類或接口,使用多態(tài)的方式來(lái)實(shí)現(xiàn)呢?還是提供配置?回調(diào)綁定?事件通知?
我們需要設(shè)計(jì)出一個(gè)好的框架,來(lái)適用于下一個(gè)項(xiàng)目,并且在一個(gè)一個(gè)的項(xiàng)目中優(yōu)化迭代,這樣才能建立深厚的沉淀、提高效率。
以上就是CocosCreator通用框架設(shè)計(jì)之網(wǎng)絡(luò)的詳細(xì)內(nèi)容,更多關(guān)于CocosCreator框架設(shè)計(jì)之網(wǎng)絡(luò)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 詳解cocoscreater預(yù)制體prefab
- 如何在CocosCreator中利用常駐節(jié)點(diǎn)做圖層管理
- 游戲開發(fā)中如何使用CocosCreator進(jìn)行音效處理
- CocosCreator ScrollView優(yōu)化系列之分幀加載
- 詳解CocosCreator項(xiàng)目結(jié)構(gòu)機(jī)制
- 如何使用CocosCreator對(duì)象池
- CocosCreator如何實(shí)現(xiàn)劃過(guò)的位置顯示紋理
- 整理CocosCreator常用知識(shí)點(diǎn)
- 全面講解CocosCreator熱更新
- CocosCreator經(jīng)典入門項(xiàng)目之flappybird
- 如何用CocosCreator實(shí)現(xiàn)射擊小游戲
- 怎樣在CocosCreator中使用游戲手柄
相關(guān)文章
js監(jiān)聽鍵盤事件的方法_原生和jquery的區(qū)別詳解
下面小編就為大家?guī)?lái)一篇js監(jiān)聽鍵盤事件的方法_原生和jquery的區(qū)別詳解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10javascript 數(shù)字格式化輸出的實(shí)現(xiàn)代碼
這篇文章主要是對(duì)javascript中數(shù)字格式化輸出的實(shí)現(xiàn)代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12下雪了 javascript實(shí)現(xiàn)雪花飛舞
下雪了,這篇文章主要介紹了javascript實(shí)現(xiàn)雪花飛舞,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04js如何實(shí)現(xiàn)點(diǎn)擊標(biāo)簽文字,文字在文本框出現(xiàn)
這篇文章主要介紹了js如何實(shí)現(xiàn)點(diǎn)擊標(biāo)簽文字,文字在文本框出現(xiàn),感興趣的小伙伴們可以參考下2015-08-08使用javascript訪問(wèn)XML數(shù)據(jù)的實(shí)例
使用javascript訪問(wèn)XML數(shù)據(jù)的實(shí)例...2006-12-12