深刻了解使用egret.WebSocket

概念

本教程不講解TCP/IP協議,Socket屬於哪層,消息包體怎麼設計等,主講 egret.WebSocket 使用示例 與 protobuf 使用示例。php

在使用egret.WebSocket以前須要簡單討論瞭解目前幾種通訊模式。html

HTTP

網站中常見的一種傳輸協議,用於訪問頁面或資源時,向頁面所在的服務器發送一個 HTTP 請求。服務器識別請求,返回響應數據並關閉鏈接。這過程當中客戶端不請求,服務器不能主動推送消息到客戶端。早些的遊戲經過輪訓以及 AJAX 實現了不須要手動刷新程序內部輪訓請求的僞的長鏈接。這顯然是一個很是不明智的方式。能夠想象一下聊天室或人物移動場景中,若是咱們使用 HTTP 會是一種什麼狀況。大量的請求與響應報頭額外的數據、延遲不斷髮生、傳輸帶寬壓力不斷增長,這對於ARPG等類型遊戲是致命的。主要適合對即時性要求不高的遊戲類型。java

Socket

端遊中常見的一種傳輸協議,套連接。須要瞭解 Socket 的同窗百度一下,它是一個長鏈接的協議。在完成握手後,鏈接會一直開着,直到客戶端或服務器明確予以關閉。在這過程當中,服務器能主動的推送消息到客戶端,消息格式能夠是進制流以及自定義格式等。後期因爲FLASH的興起,頁遊中絕大多數都在使用。能夠想象一下聊天室或人物移動場景中,咱們使用 socket 會是一種什麼狀況。沒有額外的數據、主動的消息推送、低延遲等等。python

WebScoket

早期 HTML 中並無提供 socket 的支持,大型頁遊項目依靠於 Flash 提供的 Socket API 。隨着 HTML5 的制定與完善,WebSocket 被各大瀏覽器廠商所支持。nginx

WebScoket 與 Socket 的區別在於前者提供了完善的API以及握手的機制,然後者是抽象出來的一種概念,具體的實現對於各類語言均可能不一樣,例如:咱們須要自定義協議體,控制緩存區,鏈接確認方式等。而在 WebSocket 中,每一個消息的傳輸規範都是定義好的,如消息以 0x00 字節開頭,以 0xff 結尾,中間數據採用 UTF-8 編碼格式,第一次握手必須使用 ws://xxxwss://xxx 進行,在握手成功後將協議升級爲 WebSocket 協議,進行雙工的通訊。第一次請求走的是 HTTP 請求。因爲各類規範的定義與實現,舊有的服務器 Socket 並不適用於 WebSocket 。c++

實際上,許多語言、框架和服務器都提供了 WebSocket 支持,例如:git

egret.WebSocket 使用示例

早期參與或製做遊戲項目,對下圖必定不陌生,定義消息長度位、消息號以及消息讀取規範,客戶端根據協議規範以字節形式讀取包體:github

HML5 的 WebSocket 傳輸中,並無定義進制流的傳送讀取。 egret.WebSocket 中對 HTML5 中 WebSocket 進行封裝,實現了對於進制流的傳輸。web

egret.WebSocket 默認是字符串形式接受數據,建立一個 egret.WebSocket 很是簡單,因爲 egret.WebSocket 對字節流傳輸的實現,服務器與客戶端舊有的協議很是方便移植。如下示例演示了建立 egret.WebSocket :apache

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庫。

相關文章
相關標籤/搜索