转 移动前端系列——websocket实时互动小游戏html
本来是想在写这文章以前,给你们来个二维码,让你们来感觉一下我那个狂拽酷炫叼炸天的实时互动小游戏,无奈一直没有找到一台足以hold住其气场的服务器。因此,此处可能须要你们跟随个人描述,脑补一下那高端大气上档次的画面及低调奢华有内涵交互设计:前端
真不是我故意这样的,实在是人类的语言已没法将其形容,过份的修饰描述只怕是有损其光辉闪耀的形象。此时的我,更是怀着对其满满的敬意,忐忑第敲打着键盘,为你们介绍其狂拽酷炫叼炸天是怎样造成的。
从文章的标题上,咱们不难看出,这个游戏是基于websocket。那么我就先从websocket的做用以及其优势这两个方面,给你们简单介绍一下websocket:node
其实websocket的做用,我的感受能够简单地用一句话来归纳:构建实时的Web应用
好比:web
在平常的使用web的过程当中,这种功能很是常见,好比:新浪微博的WebIM、WebQQ、大智慧网页版等等,咱们在处理平常的一些专题中,适当地加入一些多屏互动,也能很好地增长用户的参与度,加强一些现场的互动,如:斗战诛天营救悟空、神秘站等ajax
在websocket出来以前,若是咱们想实现上述类型的功能,咱们一般采用的是如下几种方式:npm
我就先经过比较以上几种方式的优缺点,让你们更为清楚地了解websocket牛B之处后端
定时向服务器发送请求,服务器响应请求并返回数据api
优势:后端服务器不须要特殊设置
缺点:易产生大量无效请求,浪费服务器资源,且消息有延迟
这种轮询的方式,在平常的网页应用中其实应用也比较多,可是只适合一些实时性要求并非很高的那种应用,好比说微博的新消息提醒,每隔一段向服务器请求,看看是否有新的微博/粉丝/@等浏览器
客户端向服务器发送Ajax请求,服务器保持该请求不中断,一直等有新的数据(或超时)须要处理才返回响应信息并关闭链接,客户端处理完成后,从新发起ajax请求缓存
PS:两张图找不一样的游戏已开始,请注意看右侧服务器端部分的差别
优势:相比轮询,减小了无效请求次数,消息的实时性获得提高
缺点:保持链接一样形成服务器资源浪费
就目前而言,大多数兼容低版本浏览器的聊天室(聊天室貌似基本玩完了)、在线客服,采用的都仍是这种方式
请求一直不中断,服务器端可不断地向客户端输出数据
优势:消息实时,不会产生无效请求
缺点:对服务器开销较大,单向接收数据还成,客户端若是想要提交数据,同样须要断开链接后从新发送请求
基于socket,服务器可客户端可随时进行双向通讯
优势:socket协议
缺点:须要安装flash player,对移动端(特别是IOS,貌似高级的安卓也在放弃flash)不友好
经过对上面四种传统方式的分析,咱们不难发现,其实前面的三种方式都是传统意义上的HTTP请求(PS:那些个乱七八糟的握手什么,就不在这里探讨了),而后你就会发现,每次的请求都会有一堆相似下面的这些个步骤
固然了,牛B的你可能会说DNS有缓存,并不会须要那么多DNS Lookup,嗯,那么其余的呢?看看那些头信息(cookie已打码)
这些信息但是会伴随每次请求,来回地穿梭在服务器和客户端之间。浪费你大量的服务器资源,固然了,弊端包括但不限于此。那么,是时候看看Websocket的优点
说到这个优势,我只想让你们看一个websocket官网上的一个图表
经过这么一个图表,咱们会发现,请求量越大的状况下,Websocket的表现就越是勇猛。与此同时,这么一个勇猛的外表下,脏着的确是一颗少女般的心。别误会,值的是学习起她来很简单。
下面就从websocket服务器及其api两个方面来简单介绍一下:
本次所讲述的websocket是基于nodejs服务器来完成整套部署的。因此,咱们须要先在服务器上搭建一个nodejs环境
直接从http://nodejs.org 这个网站上下载后直接安装就成,应该是没什么难度的
安装完成以后,咱们能够在命令行工具中运行 node -v来检测安装是否成功
若是正常地显示出了版本号,那么说明nodejs安装成功,接下来咱们就须要安装websocket模块了
Nodejs安装完成以后,其默认就给安装好了nodejs包管理工具npm,经过使用npm命令,咱们就能够来安装/卸载/更新nodejs的包。
一切正常的话,咱们就能够经过使用命令
npm install ws
来安装websocket模块
websocket的服务器环境基本搭建完成,接下来咱们经过几行简单地代码就能够把一个websocket服务器启动起来
var cons = new Array(); var ws = require('ws').Server; var server = new ws({host:"127.0.0.1",port:8808}); server.on('connection',function(ws){ console.log('new connection founded successfully'); cons.push(ws); ws.on('message',function(data){ for(var i=0;i<cons.length;i++){ cons[i].send(data); } }); ws.on('close',function(){ for(var i=0;i<cons.length;i++){ if(cons[i] == ws) cons.splice(i,1); } }); }); console.log('websocket-server running...');
保存文件名为app.js,在命令行中运行
node app.js
到此为止,服务端的部署完成,接下来,就能够看看websocket是如何在浏览器上跑起来的。
在客户端,仅须要一条语句,就算是创建起了客户端和服务器端的连接
var ws = new WebSocket('ws://127.0.0.1:8808/');
PS:所传递参数中的地址须要服务器上配置的一致
而后就能够经过各类事件/方法来完成客户端和服务器之间的数据交互,这个也就是我接下来要介绍的
固然,我这里的介绍包括了事件及方法
经常使用的事件和方法,总共为一下6个
用法大体以下
//创建服务器链接 ws.onopen = function(){ systemInfo.innerHTML = '<p>和websocket服务器链接成功</p>'; } //接收到服务器返回的数据 ws.onmessage = function(e){ systemInfo.innerHTML += '<p>'+e.data+'</p>'; } //断开服务器链接 ws.onclose = function(){ systemInfo.innerHTML += '<p>WebSocket服务器链接关闭</p>'; } //ws发生错误 ws.onerror = function(e){ console.log(e); systemInfo.innerHTML += '<p>WebSocket发生错误</p>'; } testForm.onsubmit = function(){ //发送数据给服务器 ws.send(username.value+":"+msg.value); return false; } close.addEventListener('click', function(){ ws.close(); }, false);
该完整demo能够点击此处下载
因而可知,websocket用起来真的很简单。可是这个功能相对来讲很是单一,在实际的项目过程当中,咱们所涉及到的业务逻辑可能会相对来讲复杂不少,好比说某些消息只想被某个特定的范围里面的用户接收,同时,至少在天朝,使用低版本IE浏览器或者其相同内核(Trident)的用户所占比例仍是很多,没理由把这批用户放弃,为了解决这个问题,socket.io组件便孕育而生了
Socket.io做为nodejs的一个模块,其安装方法和ws的彻底一致
npm install socket.io
Socket.io一样的简单
在服务端只须要起一个HTTP server,而后在启动socket.io便可
var app = require('http').createServer(handler) var io = require('socket.io')(app);
Handler函数本身YY一下吧,
客户端的话,比使用原生的websocket稍微多一步,须要在页面上引入一个socket.io.js文件
<script src="/socket.io/socket.io.js"></script>
而后在经过运行
var socket = io();
即创建起了socket链接.
有的童鞋可能会奇怪,在本身的代码目录中并无socket.io.js这个文件,设置在网站根目录下socket.io这个目录都没有,其缘由是这个请求被rewrite了,因此~~仅仅使用的话..你能够不用在乎这个细节,若是你只是想去看看这个文件的代码,能够直接去访问那个路径便可。
对于socket.io,咱们只须要掌握两个功能函数,便可以完成基本的websocket功能了。这两个函数分别为:
经常使用的事件
好吧,前面介绍了一堆,如今立刻回到那个狂拽酷炫叼炸天的游戏上来,介绍这个游戏的实现,我会从4个方面来进行。
记得每一个页面都须要引入socket.io.js文件
<script src="/socket.io/socket.io.js"></script>
一、用户注册/登陆
二、建立房间
三、加入房间
四、对战(实时排行榜)
本游戏是没有进行严格意义上的用户受权验证,各位就莫要纠结这些。
注册/登陆顾名思义,页面上确定就是一个表单,让用户填写一些用户名之类的。固然了,我绝对不会由于这种页面简单,就随便设计下敷衍了事。一个伟大的产品,在这些细节把握上,作得那都是很是到位。(做者此处忍住了,未用人类的语言损害其光辉闪耀的形象)
用传统的方式去完成这种注册/登陆的话,就两步:
1.客户端填好信息后,post相关信息到某个接口文件,在服务器上完成了相应的操做以后,反馈给客户端一些信息。
2.客户端接收到服务器返回的信息后,给出相应的操做或者是相关的错误提示信息
用socket的方式,步骤和这个基本一致,只不过是这个减小了一些请求的发送,其步骤也一样是两步:
1.客户端填好信息后,经过指定事件将这些数据发送到服务器端,服务端经过监听这个指定的事件,去完成相应的操做。完成以后,一样经过一个指定的事件,将消息发送回客户端。
2.客户端监听到服务器所触发的那个事件后,给出相应的操做或者是错误提示信息
在咱们的这个案例中,关键代码以下
客户端向服务器发送注册事件(写在客户端)
socket.emit('registe', userName); //事件名可自定义
服务器监听registe事件(写在服务端)
socket.on('registe', function(userName){ //完成一些重名判断,写入数据之类的 //上述步骤完成以后,须要向客户端发送事件,事件名可自定义 socket.emit('registe', { userInfo : userInfo, msg : 'registe successed', code : 0 }) });
客户端监听服务器上发送的那个事件
socket.on('registe', function (data) { //根据服务器给回的数据进行相应的操做 });
建立房间的流程和注册的流程一致,从新定义个事件名基本上就OK了。可是真当你按照上面的那些流程去操做的时候,你会发现当你停留在房间列表页的时候,你只能看到你本身刚建立的房间被动态插入到列表中。在你停留在房间列表的时候,其余用户建立的房间,你看不见。一样的,你的房间也不会实时刷新到其余用户的房间列表中。除非你手动刷新你页面。
若是,由于这点,你以为socket也就不过如此的话,那么你就是真的是小瞧socket.io了,socket.io发送消息,默认状况下是只发送给当前链接的socket,可是它也是能够把消息发送给全部人的。咱们只须要修改一点代码便可达成实时更新全部用户房间列表的功能.
下面的这几行代码是服务端建立房间的关键代码:
socket.on('create', function (data) { //完成一些重名判断,写入数据之类的 //关键代码在此,注意和上面注册的代码相比较 io.sockets.emit('create', { roomInfo : roomInfo, msg : 'create successed', code : 0 }) });
上面的注册/登陆咱们在服务器向客户端发送消息时,用到的是socket.emit
在建立房间列表时,用到的是io.sockets.emit
经过使用下面的这种方式,咱们就能够实现想全部链接的socket发送消息的功能
经过上面两个功能点的讲解,也许你立刻就想到了加入房间功能应该如何实现了,客户端发送一个加入房间的事件到服务器端,服务器给当前的这个用户一个标识,标识当前这个用户所进入的房间,而后通知到客户端就行了。确实,你这样实现也确实能够实现基本的加入房间功能,可是你别所以就关闭了我这篇文章,搞很差这里还能给你提供一个更优雅的实现方式呢!(第一次看到上面就关了,第二次才看到这里的朋友,你也是幸运的).
没错,这里就是要给你们提供一个更优雅的方式,若是你按照上面的那个思路往下进行,你会发现代码写起来彷佛愈来愈费劲。这里须要给你们介绍的就是另一个API:socket.join
从字面上咱们彷佛就发现了,这个API简直就是为加入房间而生了。没错,用他来实现加入房间,很完美。可是我的仍是建议你把他理解成为加入某个分组。相信这样,我才不会固化了你们伙的思惟。
若是是用这种方法,那么加入房间就会变得异常轻松
socket.on('enter', function(data){ //加入房间 socket.join(data.room); //加入成功以后通知客户端 socket.emit('enter', userInfo[data.user]); })
到此为止,彷佛采用这种join的方式,优点也并非那么特别的明显。那么,在接下来的对战页面中。你就能发现其牛B之处
所谓实时排行榜,就确定是服务器上有数据发生变化时,须要通知客户端去更新。前面我给你们介绍过两种发送数据的方式
socket.emit //向当前链接的socket
以及
io.sockets.emit //向全部链接的socket发送信息
可是,在实际的这种加入房间的游戏对战中,彷佛这两种发送消息的方式都不知足。第一种范围过小,光本身看到不顶用;第二种范围又太大,很容易骚扰到其余房间的用户。咱们须要第三种:消息只能被指定房间中的用户接收。很不巧的是,socket.io还真提供了这种API:
io.sockets.in(roomID).emit roomID也就是咱们上面socket.join方法中传递的参数,那么此时,咱们的代码仅须要如此: io.sockets.in(roomID).emit('update scroce', { player : roomInfo[roonName].player, userInfo : userInfo })
一样的,游戏倒计时也可使用这种方法。
socket.io提供的消息发送方式,不只仅为以上三种方式,其包含有以下几种:
好了,到此为止,这个实时对战小游戏的功能基本上介绍完毕了。 固然了,websocket就目前而言,在真正使用的时候仍是多少考虑下一些实际的问题,至少天朝带宽什么的可能并非特别的理想,网络延迟之类的仍是比较严重。不过,随着4G的出现及从此互联网的发展,兴许这之后就真不是什么问题了呢。