移動前端系列——websocket實時互動小遊戲

移動前端系列——websocket實時互動小遊戲html

本來是想在寫這文章以前,給你們來個二維碼,讓你們來感覺一下我那個狂拽酷炫叼炸天的實時互動小遊戲,無奈一直沒有找到一臺足以hold住其氣場的服務器。因此,此處可能須要你們跟隨個人描述,腦補一下那高端大氣上檔次的畫面及低調奢華有內涵交互設計:前端

  • 登陸界面(此處省略4.33W字)
  • 房間列表頁(此處省略3.75W字)
  • 遊戲界面(此處省略5.83W字)

真不是我故意這樣的,實在是人類的語言已沒法將其形容,過份的修飾描述只怕是有損其光輝閃耀的形象。此時的我,更是懷着對其滿滿的敬意,忐忑第敲打着鍵盤,爲你們介紹其狂拽酷炫叼炸天是怎樣造成的。
從文章的標題上,咱們不難看出,這個遊戲是基於websocket。那麼我就先從websocket的做用以及其優勢這兩個方面,給你們簡單介紹一下websocket:node

Websocket的做用

其實websocket的做用,我的感受能夠簡單地用一句話來歸納:構建實時的Web應用
好比:web

  • 聊天室/在線客服
  • 在線遊戲
  • 股票走勢
  • 多屏互動
  • ...

在平常的使用web的過程當中,這種功能很是常見,好比:新浪微博的WebIM、WebQQ、大智慧網頁版等等,咱們在處理平常的一些專題中,適當地加入一些多屏互動,也能很好地增長用戶的參與度,加強一些現場的互動,如:鬥戰誅天營救悟空、神祕站等ajax

clipboard.png

clipboard.png

在websocket出來以前,若是咱們想實現上述類型的功能,咱們一般採用的是如下幾種方式:npm

  • 輪詢
  • 長輪詢
  • 長鏈接
  • Flash

我就先經過比較以上幾種方式的優缺點,讓你們更爲清楚地瞭解websocket牛B之處後端

輪詢

定時向服務器發送請求,服務器響應請求並返回數據api

clipboard.png

優勢:後端服務器不須要特殊設置
缺點:易產生大量無效請求,浪費服務器資源,且消息有延遲
這種輪詢的方式,在平常的網頁應用中其實應用也比較多,可是隻適合一些實時性要求並非很高的那種應用,好比說微博的新消息提醒,每隔一段向服務器請求,看看是否有新的微博/粉絲/@等瀏覽器

長輪詢

客戶端向服務器發送Ajax請求,服務器保持該請求不中斷,一直等有新的數據(或超時)須要處理才返回響應信息並關閉鏈接,客戶端處理完成後,從新發起ajax請求緩存

clipboard.png

PS:兩張圖找不一樣的遊戲已開始,請注意看右側服務器端部分的差別

優勢:相比輪詢,減小了無效請求次數,消息的實時性獲得提高
缺點:保持鏈接一樣形成服務器資源浪費
就目前而言,大多數兼容低版本瀏覽器的聊天室(聊天室貌似基本玩完了)、在線客服,採用的都仍是這種方式

長鏈接

請求一直不中斷,服務器端可不斷地向客戶端輸出數據

clipboard.png

優勢:消息實時,不會產生無效請求
缺點:對服務器開銷較大,單向接收數據還成,客戶端若是想要提交數據,同樣須要斷開鏈接後從新發送請求

Flash

基於socket,服務器可客戶端可隨時進行雙向通訊

clipboard.png

優勢:socket協議
缺點:須要安裝flash player,對移動端(特別是IOS,貌似高級的安卓也在放棄flash)不友好

經過對上面四種傳統方式的分析,咱們不難發現,其實前面的三種方式都是傳統意義上的HTTP請求(PS:那些個亂七八糟的握手什麼,就不在這裏探討了),而後你就會發現,每次的請求都會有一堆相似下面的這些個步驟

clipboard.png

固然了,牛B的你可能會說DNS有緩存,並不會須要那麼多DNS Lookup,嗯,那麼其餘的呢?看看那些頭信息(cookie已打碼)

clipboard.png

這些信息但是會伴隨每次請求,來回地穿梭在服務器和客戶端之間。浪費你大量的服務器資源,固然了,弊端包括但不限於此。那麼,是時候看看Websocket的優點

Websocket的優勢

說到這個優勢,我只想讓你們看一個websocket官網上的一個圖表

clipboard.png

經過這麼一個圖表,咱們會發現,請求量越大的狀況下,Websocket的表現就越是勇猛。與此同時,這麼一個勇猛的外表下,髒着的確是一顆少女般的心。別誤會,值的是學習起她來很簡單。
下面就從websocket服務器及其api兩個方面來簡單介紹一下:

Webscoket服務器的搭建

本次所講述的websocket是基於nodejs服務器來完成整套部署的。因此,咱們須要先在服務器上搭建一個nodejs環境

Nodejs安裝

直接從http://nodejs.org 這個網站上下載後直接安裝就成,應該是沒什麼難度的

clipboard.png

安裝完成以後,咱們能夠在命令行工具中運行 node -v來檢測安裝是否成功

clipboard.png

若是正常地顯示出了版本號,那麼說明nodejs安裝成功,接下來咱們就須要安裝websocket模塊了

Websocket模塊安裝

Nodejs安裝完成以後,其默認就給安裝好了nodejs包管理工具npm,經過使用npm命令,咱們就能夠來安裝/卸載/更新nodejs的包。

一切正常的話,咱們就能夠經過使用命令

npm install ws

來安裝websocket模塊

clipboard.png

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:所傳遞參數中的地址須要服務器上配置的一致
而後就能夠經過各類事件/方法來完成客戶端和服務器之間的數據交互,這個也就是我接下來要介紹的

Websocket API簡介

固然,我這裏的介紹包括了事件及方法
經常使用的事件和方法,總共爲一下6個

  • onopen 和服務器鏈接成功
  • onmessage 接收服務器的消息
  • onclose 斷開和服務器的連接
  • onerror 錯誤處理
  • send 向服務器發送消息
  • close 斷開和服務器的連接

用法大體以下

//創建服務器鏈接
       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

Socket.io做爲nodejs的一個模塊,其安裝方法和ws的徹底一致

npm install socket.io

clipboard.png

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功能了。這兩個函數分別爲:

  • on 事件監聽
  • emit 觸發事件

經常使用的事件

  • connect 創建鏈接
  • disconnect 斷開鏈接
  • error 出錯

實時聯機小遊戲

好吧,前面介紹了一堆,如今立刻回到那個狂拽酷炫叼炸天的遊戲上來,介紹這個遊戲的實現,我會從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提供的消息發送方式,不只僅爲以上三種方式,其包含有以下幾種:

  • socket.emit() //發送消息給當前請求的socket
  • io.sockets.emit() //發送消息給全部鏈接socket
  • socket.broadcase.emit() //發送消息給當前請求以外的全部的socket
  • io.sockets.in(foo).emit() //向指定的分組發送消息
  • socket.broadcase.to(foo).emit() //向指定的分組發送消息,除當前請求的socket
  • io.sockets.socket(socketid).emit() //經過socketid向特定有效的socket發送消息

好了,到此爲止,這個實時對戰小遊戲的功能基本上介紹完畢了。 固然了,websocket就目前而言,在真正使用的時候仍是多少考慮下一些實際的問題,至少天朝帶寬什麼的可能並非特別的理想,網絡延遲之類的仍是比較嚴重。不過,隨着4G的出現及從此互聯網的發展,興許這之後就真不是什麼問題了呢。

相關文章
相關標籤/搜索