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