本教程不讲解TCP/IP协议,Socket属于哪层,消息包体怎么设计等,主讲 egret.WebSocket 使用示例 与 protobuf 使用示例。php
在使用egret.WebSocket以前须要简单讨论了解目前几种通讯模式。html
网站中常见的一种传输协议,用于访问页面或资源时,向页面所在的服务器发送一个 HTTP 请求。服务器识别请求,返回响应数据并关闭链接。这过程当中客户端不请求,服务器不能主动推送消息到客户端。早些的游戏经过轮训以及 AJAX 实现了不须要手动刷新程序内部轮训请求的伪的长链接。这显然是一个很是不明智的方式。能够想象一下聊天室或人物移动场景中,若是咱们使用 HTTP 会是一种什么状况。大量的请求与响应报头额外的数据、延迟不断发生、传输带宽压力不断增长,这对于ARPG等类型游戏是致命的。主要适合对即时性要求不高的游戏类型。java
端游中常见的一种传输协议,套连接。须要了解 Socket 的同窗百度一下,它是一个长链接的协议。在完成握手后,链接会一直开着,直到客户端或服务器明确予以关闭。在这过程当中,服务器能主动的推送消息到客户端,消息格式能够是进制流以及自定义格式等。后期因为FLASH的兴起,页游中绝大多数都在使用。能够想象一下聊天室或人物移动场景中,咱们使用 socket 会是一种什么状况。没有额外的数据、主动的消息推送、低延迟等等。python
早期 HTML 中并无提供 socket 的支持,大型页游项目依靠于 Flash 提供的 Socket API 。随着 HTML5 的制定与完善,WebSocket 被各大浏览器厂商所支持。nginx
WebScoket 与 Socket 的区别在于前者提供了完善的API以及握手的机制,然后者是抽象出来的一种概念,具体的实现对于各类语言均可能不一样,例如:咱们须要自定义协议体,控制缓存区,链接确认方式等。而在 WebSocket 中,每一个消息的传输规范都是定义好的,如消息以 0x00 字节开头,以 0xff 结尾,中间数据采用 UTF-8 编码格式,第一次握手必须使用 ws://xxx 或 wss://xxx 进行,在握手成功后将协议升级为 WebSocket 协议,进行双工的通讯。第一次请求走的是 HTTP 请求。因为各类规范的定义与实现,旧有的服务器 Socket 并不适用于 WebSocket 。c++
实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如:git
早期参与或制做游戏项目,对下图必定不陌生,定义消息长度位、消息号以及消息读取规范,客户端根据协议规范以字节形式读取包体: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();
百度百科 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库。