In 網(wǎng)頁(yè)重構(gòu) on 2014-12-09 14:54:36 by 鬼爪手
原本是想在寫(xiě)這文章之前,給大家來(lái)個(gè)二維碼,讓大家來(lái)感受一下我那個(gè)狂拽酷炫叼炸天的實(shí)時(shí)互動(dòng)小游戲,無(wú)奈一直沒(méi)有找到一臺(tái)足以hold住其氣場(chǎng)的服務(wù)器。所以,此處可能需要大家跟隨我的描述,腦補(bǔ)一下那高端大氣上檔次的畫(huà)面及低調(diào)奢華有內(nèi)涵交互設(shè)計(jì):
1、登錄界面
(此處省略4.33W字)
2、房間列表頁(yè)
(此處省略3.75W字)
3、游戲界面
(此處省略5.83W字)
真不是我故意這樣的,實(shí)在是人類(lèi)的語(yǔ)言已無(wú)法將其形容,過(guò)份的修飾描述只怕是有損其光輝閃耀的形象。此時(shí)的我,更是懷著對(duì)其滿滿的敬意,忐忑第敲打著鍵盤(pán),為大家介紹其狂拽酷炫叼炸天是怎樣形成的。
從文章的標(biāo)題上,我們不難看出,這個(gè)游戲是基于websocket。那么我就先從websocket的作用以及其優(yōu)點(diǎn)這兩個(gè)方面,給大家簡(jiǎn)單介紹一下websocket:
Websocket的作用
其實(shí)websocket的作用,個(gè)人感覺(jué)可以簡(jiǎn)單地用一句話來(lái)概括:
構(gòu)建實(shí)時(shí)的Web應(yīng)用
比如:
1、聊天室/在線客服
2、在線游戲
3、股票走勢(shì)
4、多屏互動(dòng)
5、...
在日常的使用web的過(guò)程中,這種功能非常常見(jiàn),比如:新浪微博的WebIM、WebQQ、大智慧網(wǎng)頁(yè)版等等,我們?cè)谔幚砣粘5囊恍?zhuān)題中,適當(dāng)?shù)丶尤胍恍┒嗥粱?dòng),也能很好地增加用戶(hù)的參與度,增強(qiáng)一些現(xiàn)場(chǎng)的互動(dòng),如:斗戰(zhàn)誅天營(yíng)救悟空、神秘站等
在websocket出來(lái)之前,如果我們想實(shí)現(xiàn)上述類(lèi)型的功能,我們通常采用的是以下幾種方式:
1、輪詢(xún)
2、長(zhǎng)輪詢(xún)
3、長(zhǎng)連接
4、Flash
我就先通過(guò)比較以上幾種方式的優(yōu)缺點(diǎn),讓大家更為清楚地了解websocket牛B之處
輪詢(xún):
定時(shí)向服務(wù)器發(fā)送請(qǐng)求,服務(wù)器響應(yīng)請(qǐng)求并返回?cái)?shù)據(jù)
優(yōu)點(diǎn):后端服務(wù)器不需要特殊設(shè)置
缺點(diǎn):易產(chǎn)生大量無(wú)效請(qǐng)求,浪費(fèi)服務(wù)器資源,且消息有延遲
這種輪詢(xún)的方式,在日常的網(wǎng)頁(yè)應(yīng)用中其實(shí)應(yīng)用也比較多,但是只適合一些實(shí)時(shí)性要求并不是很高的那種應(yīng)用,比如說(shuō)微博的新消息提醒,每隔一段向服務(wù)器請(qǐng)求,看看是否有新的微博/粉絲/@等
長(zhǎng)輪詢(xún)
客戶(hù)端向服務(wù)器發(fā)送Ajax請(qǐng)求,服務(wù)器保持該請(qǐng)求不中斷,一直等有新的數(shù)據(jù)(或超時(shí))需要處理才返回響應(yīng)信息并關(guān)閉連接,客戶(hù)端處理完成后,重新發(fā)起ajax請(qǐng)求
PS:兩張圖找不同的游戲已開(kāi)始,請(qǐng)注意看右側(cè)服務(wù)器端部分的差異
優(yōu)點(diǎn):相比輪詢(xún),減少了無(wú)效請(qǐng)求次數(shù),消息的實(shí)時(shí)性得到提升
缺點(diǎn):保持連接同樣造成服務(wù)器資源浪費(fèi)
就目前而言,大多數(shù)兼容低版本瀏覽器的聊天室(聊天室貌似基本玩完了)、在線客服,采用的都還是這種方式
長(zhǎng)連接
請(qǐng)求一直不中斷,服務(wù)器端可不斷地向客戶(hù)端輸出數(shù)據(jù)
優(yōu)點(diǎn):消息實(shí)時(shí),不會(huì)產(chǎn)生無(wú)效請(qǐng)求
缺點(diǎn):對(duì)服務(wù)器開(kāi)銷(xiāo)較大,單向接收數(shù)據(jù)還成,客戶(hù)端如果想要提交數(shù)據(jù),一樣需要斷開(kāi)連接后重新發(fā)送請(qǐng)求
Flash
基于socket,服務(wù)器可客戶(hù)端可隨時(shí)進(jìn)行雙向通信
優(yōu)點(diǎn):socket協(xié)議
缺點(diǎn):需要安裝flash player,對(duì)移動(dòng)端(特別是IOS,貌似高級(jí)的安卓也在放棄flash)不友好
通過(guò)對(duì)上面四種傳統(tǒng)方式的分析,我們不難發(fā)現(xiàn),其實(shí)前面的三種方式都是傳統(tǒng)意義上的HTTP請(qǐng)求(PS:那些個(gè)亂七八糟的握手什么,就不在這里探討了),然后你就會(huì)發(fā)現(xiàn),每次的請(qǐng)求都會(huì)有一堆類(lèi)似下面的這些個(gè)步驟
當(dāng)然了,牛B的你可能會(huì)說(shuō)DNS有緩存,并不會(huì)需要那么多DNS Lookup,嗯,那么其他的呢?看看那些頭信息(cookie已打碼)
這些信息可是會(huì)伴隨每次請(qǐng)求,來(lái)回地穿梭在服務(wù)器和客戶(hù)端之間。浪費(fèi)你大量的服務(wù)器資源,當(dāng)然了,弊端包括但不限于此。那么,是時(shí)候看看Websocket的優(yōu)勢(shì)
Websocket的優(yōu)點(diǎn)
說(shuō)到這個(gè)優(yōu)點(diǎn),我只想讓大家看一個(gè)websocket官網(wǎng)上的一個(gè)圖表
通過(guò)這么一個(gè)圖表,我們會(huì)發(fā)現(xiàn),請(qǐng)求量越大的情況下,Websocket的表現(xiàn)就越是勇猛。與此同時(shí),這么一個(gè)勇猛的外表下,臟著的確是一顆少女般的心。別誤會(huì),值的是學(xué)習(xí)起她來(lái)很簡(jiǎn)單。
下面就從websocket服務(wù)器及其api兩個(gè)方面來(lái)簡(jiǎn)單介紹一下:
Webscoket服務(wù)器的搭建
本次所講述的websocket是基于nodejs服務(wù)器來(lái)完成整套部署的。所以,我們需要先在服務(wù)器上搭建一個(gè)nodejs環(huán)境
Nodejs安裝
直接從http://nodejs.org 這個(gè)網(wǎng)站上下載后直接安裝就成,應(yīng)該是沒(méi)什么難度的
安裝完成之后,我們可以在命令行工具中運(yùn)行 node -v來(lái)檢測(cè)安裝是否成功
如果正常地顯示出了版本號(hào),那么說(shuō)明nodejs安裝成功,接下來(lái)我們就需要安裝websocket模塊了
Websocket模塊安裝
Nodejs安裝完成之后,其默認(rèn)就給安裝好了nodejs包管理工具npm,通過(guò)使用npm命令,我們就可以來(lái)安裝/卸載/更新nodejs的包。
一切正常的話,我們就可以通過(guò)使用命令
npm install ws
來(lái)安裝websocket模塊
websocket的服務(wù)器環(huán)境基本搭建完成,接下來(lái)我們通過(guò)幾行簡(jiǎn)單地代碼就可以把一個(gè)websocket服務(wù)器啟動(dòng)起來(lái)
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,在命令行中運(yùn)行
node app.js
到此為止,服務(wù)端的部署完成,接下來(lái),就可以看看websocket是如何在瀏覽器上跑起來(lái)的。
在客戶(hù)端,僅需要一條語(yǔ)句,就算是建立起了客戶(hù)端和服務(wù)器端的鏈接
var ws = new WebSocket('ws://127.0.0.1:8808/');
PS:所傳遞參數(shù)中的地址需要服務(wù)器上配置的一致
然后就可以通過(guò)各種事件/方法來(lái)完成客戶(hù)端和服務(wù)器之間的數(shù)據(jù)交互,這個(gè)也就是我接下來(lái)要介紹的
Websocket API簡(jiǎn)介
當(dāng)然,我這里的介紹包括了事件及方法
常用的事件和方法,總共為一下6個(gè)
onopen 和服務(wù)器連接成功
onmessage 接收服務(wù)器的消息
onclose 斷開(kāi)和服務(wù)器的鏈接
onerror 錯(cuò)誤處理
send 向服務(wù)器發(fā)送消息
close 斷開(kāi)和服務(wù)器的鏈接
用法大致如下
//建立服務(wù)器連接
ws.onopen = function(){
systemInfo.innerHTML = '<p>和websocket服務(wù)器連接成功</p>';
}
//接收到服務(wù)器返回的數(shù)據(jù)
ws.onmessage = function(e){
systemInfo.innerHTML += '<p>'+e.data+'</p>';
}
//斷開(kāi)服務(wù)器連接
ws.onclose = function(){
systemInfo.innerHTML += '<p>WebSocket服務(wù)器連接關(guān)閉</p>';
}
//ws發(fā)生錯(cuò)誤
ws.onerror = function(e){
console.log(e);
systemInfo.innerHTML += '<p>WebSocket發(fā)生錯(cuò)誤</p>';
}
testForm.onsubmit = function(){
//發(fā)送數(shù)據(jù)給服務(wù)器
ws.send(username.value+":"+msg.value);
return false;
}
close.addEventListener('click', function(){
ws.close();
}, false);
該完整demo可以點(diǎn)擊此處下載
由此可見(jiàn),websocket用起來(lái)真的很簡(jiǎn)單。但是這個(gè)功能相對(duì)來(lái)說(shuō)非常單一,在實(shí)際的項(xiàng)目過(guò)程中,我們所涉及到的業(yè)務(wù)邏輯可能會(huì)相對(duì)來(lái)說(shuō)復(fù)雜很多,比如說(shuō)某些消息只想被某個(gè)特定的范圍里面的用戶(hù)接收,同時(shí),至少在天朝,使用低版本IE瀏覽器或者其相同內(nèi)核(Trident)的用戶(hù)所占比例還是不少,沒(méi)理由把這批用戶(hù)放棄,為了解決這個(gè)問(wèn)題,socket.io組件便孕育而生了
Socket.io
Socket.io作為nodejs的一個(gè)模塊,其安裝方法和ws的完全一致
npm install socket.io
Socket.io同樣的簡(jiǎn)單
在服務(wù)端只需要起一個(gè)HTTP server,然后在啟動(dòng)socket.io即可
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
Handler函數(shù)自己YY一下吧,
客戶(hù)端的話,比使用原生的websocket稍微多一步,需要在頁(yè)面上引入一個(gè)socket.io.js文件
<script src="/socket.io/socket.io.js"></script>
然后在通過(guò)運(yùn)行
var socket = io();
即建立起了socket連接
有的童鞋可能會(huì)奇怪,在自己的代碼目錄中并沒(méi)有socket.io.js這個(gè)文件,設(shè)置在網(wǎng)站根目錄下socket.io這個(gè)目錄都沒(méi)有,其原因是這個(gè)請(qǐng)求被rewrite了,所以~~僅僅使用的話..你可以不用在意這個(gè)細(xì)節(jié),如果你只是想去看看這個(gè)文件的代碼,可以直接去訪問(wèn)那個(gè)路徑即可。
對(duì)于socket.io,我們只需要掌握兩個(gè)功能函數(shù),即可以完成基本的websocket功能了。
這兩個(gè)函數(shù)分別為
on 事件監(jiān)聽(tīng)
emit 觸發(fā)事件
常用的事件
connect 建立連接
disconnect 斷開(kāi)連接
error 出錯(cuò)
實(shí)時(shí)聯(lián)機(jī)小游戲
好吧,前面介紹了一堆,現(xiàn)在馬上回到那個(gè)狂拽酷炫叼炸天的游戲上來(lái),介紹這個(gè)游戲的實(shí)現(xiàn),我會(huì)從4個(gè)方面來(lái)進(jìn)行。
記得每個(gè)頁(yè)面都需要引入socket.io.js文件
<script src="/socket.io/socket.io.js"></script>
1、用戶(hù)注冊(cè)/登錄
2、創(chuàng)建房間
3、加入房間
4、對(duì)戰(zhàn)(實(shí)時(shí)排行榜)
用戶(hù)注冊(cè)/登錄
本游戲是沒(méi)有進(jìn)行嚴(yán)格意義上的用戶(hù)授權(quán)驗(yàn)證,各位就莫要糾結(jié)這些。
注冊(cè)/登錄顧名思義,頁(yè)面上肯定就是一個(gè)表單,讓用戶(hù)填寫(xiě)一些用戶(hù)名之類(lèi)的。當(dāng)然了,我絕對(duì)不會(huì)因?yàn)檫@種頁(yè)面簡(jiǎn)單,就隨便設(shè)計(jì)下敷衍了事。一個(gè)偉大的產(chǎn)品,在這些細(xì)節(jié)把握上,做得那都是非常到位。(作者此處忍住了,未用人類(lèi)的語(yǔ)言損害其光輝閃耀的形象)
用傳統(tǒng)的方式去完成這種注冊(cè)/登錄的話,就兩部:
1、客戶(hù)端填好信息后,post相關(guān)信息到某個(gè)接口文件,在服務(wù)器上完成了相應(yīng)的操作之后,反饋給客戶(hù)端一些信息。
2、客戶(hù)端接收到服務(wù)器返回的信息后,給出相應(yīng)的操作或者是相關(guān)的錯(cuò)誤提示信息
用socket的方式,步驟和這個(gè)基本一致,只不過(guò)是這個(gè)減少了一些請(qǐng)求的發(fā)送,其步驟也同樣是兩部
1、客戶(hù)端填好信息后,通過(guò)指定事件將這些數(shù)據(jù)發(fā)送到服務(wù)器端,服務(wù)端通過(guò)監(jiān)聽(tīng)這個(gè)指定的事件,去完成相應(yīng)的操作。完成之后,同樣通過(guò)一個(gè)指定的事件,將消息發(fā)送回客戶(hù)端。
2、客戶(hù)端監(jiān)聽(tīng)到服務(wù)器所觸發(fā)的那個(gè)事件后,給出相應(yīng)的操作或者是錯(cuò)誤提示信息
在我們的這個(gè)案例中,關(guān)鍵代碼如下
客戶(hù)端向服務(wù)器發(fā)送注冊(cè)事件(寫(xiě)在客戶(hù)端)
socket.emit('registe', userName); //事件名可自定義
服務(wù)器監(jiān)聽(tīng)registe事件(寫(xiě)在服務(wù)端)
socket.on('registe', function(userName){
//完成一些重名判斷,寫(xiě)入數(shù)據(jù)之類(lèi)的
//上述步驟完成之后,需要向客戶(hù)端發(fā)送事件,事件名可自定義
socket.emit('registe', {
userInfo : userInfo,
msg : 'registe successed',
code : 0
})
});
客戶(hù)端監(jiān)聽(tīng)服務(wù)器上發(fā)送的那個(gè)事件
socket.on('registe', function (data) {
//根據(jù)服務(wù)器給回的數(shù)據(jù)進(jìn)行相應(yīng)的操作
});
創(chuàng)建房間
創(chuàng)建房間的流程和注冊(cè)的流程一致,重新定義個(gè)事件名基本上就OK了。但是真當(dāng)你按照上面的那些流程去操作的時(shí)候,你會(huì)發(fā)現(xiàn)當(dāng)你停留在房間列表頁(yè)的時(shí)候,你只能看到你自己剛創(chuàng)建的房間被動(dòng)態(tài)插入到列表中。在你停留在房間列表的時(shí)候,其他用戶(hù)創(chuàng)建的房間,你看不見(jiàn)。同樣的,你的房間也不會(huì)實(shí)時(shí)刷新到其他用戶(hù)的房間列表中。除非你手動(dòng)刷新你頁(yè)面。
如果,因?yàn)檫@點(diǎn),你覺(jué)得socket也就不過(guò)如此的話,那么你就是真的是小瞧socket.io了,socket.io發(fā)送消息,默認(rèn)情況下是只發(fā)送給當(dāng)前連接的socket,但是它也是可以把消息發(fā)送給所有人的。我們只需要修改一點(diǎn)代碼即可達(dá)成實(shí)時(shí)更新所有用戶(hù)房間列表的功能
下面的這幾行代碼是服務(wù)端創(chuàng)建房間的關(guān)鍵代碼
socket.on('create', function (data) {
//完成一些重名判斷,寫(xiě)入數(shù)據(jù)之類(lèi)的
//關(guān)鍵代碼在此,注意和上面注冊(cè)的代碼相比較
io.sockets.emit('create', {
roomInfo : roomInfo,
msg : 'create successed',
code : 0
})
});
上面的注冊(cè)/登錄我們?cè)诜?wù)器向客戶(hù)端發(fā)送消息時(shí),用到的是
socket.emit
在創(chuàng)建房間列表時(shí),用到的是
io.sockets.emit
通過(guò)使用下面的這種方式,我們就可以實(shí)現(xiàn)想所有連接的socket發(fā)送消息的功能
加入房間
通過(guò)上面兩個(gè)功能點(diǎn)的講解,也許你馬上就想到了加入房間功能應(yīng)該如何實(shí)現(xiàn)了,客戶(hù)端發(fā)送一個(gè)加入房間的事件到服務(wù)器端,服務(wù)器給當(dāng)前的這個(gè)用戶(hù)一個(gè)標(biāo)識(shí),標(biāo)識(shí)當(dāng)前這個(gè)用戶(hù)所進(jìn)入的房間,然后通知到客戶(hù)端就好了。確實(shí),你這樣實(shí)現(xiàn)也確實(shí)可以實(shí)現(xiàn)基本的加入房間功能,但是你別因此就關(guān)閉了我這篇文章,搞不好這里還能給你提供一個(gè)更優(yōu)雅的實(shí)現(xiàn)方式呢?。ǖ谝淮慰吹缴厦婢完P(guān)了,第二次才看到這里的朋友,你也是幸運(yùn)的)
沒(méi)錯(cuò),這里就是要給大家提供一個(gè)更優(yōu)雅的方式,如果你按照上面的那個(gè)思路往下進(jìn)行,你會(huì)發(fā)現(xiàn)代碼寫(xiě)起來(lái)似乎越來(lái)越費(fèi)勁。這里需要給大家介紹的就是另外一個(gè)API:
socket.join
從字面上我們似乎就發(fā)現(xiàn)了,這個(gè)API簡(jiǎn)直就是為加入房間而生了。沒(méi)錯(cuò),用他來(lái)實(shí)現(xiàn)加入房間,很完美。但是個(gè)人還是建議你把他理解成為加入某個(gè)分組。相信這樣,我才不會(huì)固化了大家伙的思維。
如果是用這種方法,那么加入房間就會(huì)變得異常輕松
socket.on('enter', function(data){
//加入房間
socket.join(data.room);
//加入成功之后通知客戶(hù)端
socket.emit('enter', userInfo[data.user]);
})
到此為止,似乎采用這種join的方式,優(yōu)勢(shì)也并不是那么特別的明顯。那么,在接下來(lái)的對(duì)戰(zhàn)頁(yè)面中。你就能發(fā)現(xiàn)其牛B之處
對(duì)戰(zhàn)(實(shí)時(shí)排行榜)
所謂實(shí)時(shí)排行榜,就肯定是服務(wù)器上有數(shù)據(jù)發(fā)生變化時(shí),需要通知客戶(hù)端去更新。前面我給大家介紹過(guò)兩種發(fā)送數(shù)據(jù)的方式
socket.emit //向當(dāng)前連接的socket
以及
io.sockets.emit //向所有連接的socket發(fā)送信息
但是,在實(shí)際的這種加入房間的游戲?qū)?zhàn)中,似乎這兩種發(fā)送消息的方式都不滿足。第一種范圍太小,光自己看到不頂用;第二種范圍又太大,很容易騷擾到其他房間的用戶(hù)。我們需要第三種:消息只能被指定房間中的用戶(hù)接收。很不巧的是,socket.io還真提供了這種API:
io.sockets.in(roomID).emit
roomID也就是我們上面socket.join方法中傳遞的參數(shù),那么此時(shí),我們的代碼僅需要如此:
io.sockets.in(roomID).emit('update scroce', {
player : roomInfo[roonName].player,
userInfo : userInfo
})
同樣的,游戲倒計(jì)時(shí)也可以使用這種方法。
socket.io提供的消息發(fā)送方式,不僅僅為以上三種方式,其包含有如下幾種:
socket.emit() //發(fā)送消息給當(dāng)前請(qǐng)求的socket
io.sockets.emit() //發(fā)送消息給所有連接socket
socket.broadcase.emit() //發(fā)送消息給當(dāng)前請(qǐng)求之外的所有的socket
io.sockets.in(foo).emit() //向指定的分組發(fā)送消息
socket.broadcase.to(foo).emit() //向指定的分組發(fā)送消息,除當(dāng)前請(qǐng)求的socket
io.sockets.socket(socketid).emit() //通過(guò)socketid向特定有效的socket發(fā)送消息
好了,到此為止,這個(gè)實(shí)時(shí)對(duì)戰(zhàn)小游戲的功能基本上介紹完畢了。當(dāng)然了,并不是所有的人設(shè)計(jì)感都像我那么強(qiáng),可以把這么一個(gè)小游戲真正做得和我的那個(gè)一樣高端大氣上檔次,低調(diào)奢華有內(nèi)涵是吧...。
什么?當(dāng)時(shí)你參加了大講堂?體驗(yàn)過(guò)我的那個(gè)小游戲?
大俠饒命,我保證以后不裝逼了行不?你得保證不砍死我。
胡扯到此結(jié)束,功能實(shí)現(xiàn)悉數(shù)奉上,一個(gè)好的產(chǎn)品確實(shí)是離不開(kāi)一個(gè)好的創(chuàng)意和一個(gè)好的設(shè)計(jì)。期待你那真正高端大氣上檔次的產(chǎn)品出現(xiàn)。
當(dāng)然了,websocket就目前而言,在真正使用的時(shí)候還是多少考慮下一些實(shí)際的問(wèn)題,至少天朝帶寬什么的可能并不是特別的理想,網(wǎng)絡(luò)延遲之類(lèi)的還是比較嚴(yán)重。不過(guò),隨著4G的出現(xiàn)及今后互聯(lián)網(wǎng)的發(fā)展,興許這以后就真不是什么問(wèn)題了呢。
聯(lián)系客服