微信小程式開發之websocket實例詳解

Y2J
發布: 2017-04-18 17:58:45
原創
4833 人瀏覽過

為什麼需要websocket?

傳統的即時互動的遊戲,或伺服器主動發送訊息的行為(如推播服務),如果想做在微信上,可能你會使用輪詢的方式進行,不過這太消耗資源,大量的請求也加重了伺服器的負擔,而且延遲問題比較嚴重。如果是自己開發的app,為了解決這些問題,很多團隊會自建socket,使用tcp長連結、自訂協定的方式與伺服器進行相對即時的資料互動。有能力的團隊,採用這種方式自然沒什麼大問題。不過小團隊可能要花很多時間去調試,要解決很多難題,這個在成本上就劃不來。

H5引進了webSocket來解決網頁端的長連結問題,而微信小程式也支援websocket。這是一個非常重要的特性,所以本系列的文章會特別拿出一篇來討論websocket。

webSocket本質上也是TCP連接,它提供全雙工的資料傳輸。一方面可以避免輪詢帶來的連接頻繁建立與斷開的性能損耗,另一方面數據可以是比較實時的進行雙向傳輸(因為是長鏈接),而且WebSocket允許跨域通信(這裡有個潛在的跨域安全性的問題,得靠服務端來解決)。目前除IE外的瀏覽器已經對webSocket支援得很好了,微信小程式再推一把之後,它會變得更加流行。

我們來設計一個新的demo,一個比較有趣的小遊戲,多人版掃雷,準確地講,多人版挖黃金。

微信小程式開發之websocket實例詳解遊戲規則是這樣的:把雷換成金子,挖到金子加一分,每人輪流一次(A挖完輪到B,B挖完A才能再點擊),點中金子就算你的,也不會炸,遊戲繼續,直到把場上所有的金子都挖完遊戲才結束。跟掃雷一樣,數字也是表示週邊有幾個金子,然後用戶根據場上已經翻出來的數字來猜哪一格可能有金子。

這種互動的遊戲困難在於,使用者的點擊操作都要傳到伺服器上,而且伺服器要即時的推送到其它玩家的應用程式上。另外用戶自己也要接收對方操作時即時傳過來的數據,這樣才不至於重複點中同一個格子。簡單講,就是你要上報操作給伺服器,而伺服器也要即時給你推訊息。為了簡化整個模型,我們規定玩家必須輪流點擊,玩家A點完後,才能輪到玩家B,玩家B操作完,玩家A才能點。

我們分幾步來實現這個功能。

一、實作想法

1、第一步,我們要先生成掃雷的地圖場景

這個演算法比較簡單,簡述一下。隨機取某行某列就可以定位一個格子,標記成金子(-1表示金子)。 mimeCnt表示要產生的金子的數量,用同樣的方式循環標記mimeCnt個隨機格子。生成完後,再用一個循環去掃描這些-1的格子,把它週邊的格子都加1,當然必須是非金子的格子才加1。代碼放在 這裡 。

微信小程式開發之websocket實例詳解

其中increaseArround用來把這格金子週邊的格子都加1,實作也比較簡單:

微信小程式開發之websocket實例詳解

執行genMimeArr(),隨機產生結果如下:

微信小程式開發之websocket實例詳解

-1表示金子。看了下貌似沒什麼問題。接下去,我們就要接入webSocket了。

(這個是js版本的,其實生成地圖場景的工作是在後台生成,這個js版本只是一個演示,不過演算法是一樣的。)

2、我們需要一個支援webSocket的服務端

本範例中,我們使用python的tornado框架來實作(tornado提供了tornado.websocket模組)。當然讀者也可以使用socket.io,專為webSocket設計的js語言的服務端,用起來非常簡單,它也對不支援webSocket的瀏覽器提供了相容(flash或comet實作)。

筆者本人比較喜歡使用tornado,做了幾年後台開發,使用最多的框架之一的就是它,NIO模型,而且非常輕量級,同樣的rps,java可能需要700-800M的內存,tornado只要30-40M,所以在一台4G內存的機子上可以跑上百個tornado服務,而java,對不起,只能跑3個虛擬機。微服務的時代,這點對小公司很重要。當然如果讀者本人對java比較熟悉的話,也可以選擇netty框架來嘗試。

webSocket用tornado的另一個好處是,它可以在同一個服務(連接埠)上同時支援webSocket及http兩種協定。 tornado的官方demo程式碼中展示了怎麼實作同時使用兩種協定。在本遊戲中,可以這麼用:使用者進入首頁,用http協議去拉取目前的房間號碼及資料。因為首頁是打開最多的,進了首頁的用戶不一定會玩遊戲。所以首頁還沒必要建立webSocket鏈接,webSocket鏈接主要用來解決頻繁請求及推送的操作。首頁只有一個請求操作。選了房間號碼後,進去下一個遊戲頁面再開始建立webSocket連結。

3、客戶端

使用微信小程式開發工具,直接連線是會報網域安全錯誤的,因為工具內部做了限制,對安全網域才會允許連線。所以同樣的,這裡我們也繼續改下工具的源碼,把相關的行改掉就行修改方式如下:

#找到asdebug.js的這一行,把它改成: if(false)即可。

`if (!i(r, "webscoket"))
登入後複製

懶得修改的讀者可以直接使用我破解過的IDE。 發起一個websocket連結的程式碼也比較簡單:

`wx.connectSocket({
url: webSocketUrl,
});
登入後複製
在调用这个请求代码之前,先添加下事件监听,这样才知道有没有连接成功:<br /> `
登入後複製

wx.onSocketOpen(function(res){ console.log('websocket opened.'); });

rreee
`连接失败的事件:
登入後複製

1的訊息時觸發的事件:

wx.onSocketError(function(res){ console.log(&#39;websocket fail&#39;);
}) `
登入後複製
`
wx.onSocketMessage(function(res){
console.log(&#39;received msg: &#39; + res.data);
})
登入後複製
訊息發送

由於建立連結是需要幾次握手,需要一定的時間,所以在wx.connectSocket成功之前,如果直接wx.sendSocketMessage發送訊息會報錯,這裡做一個兼容,如果連接還沒建立成功,則用一個數組來保存要發送的信息;而鏈接第一次建立時,把數據遍歷一遍,把消息拿出來一個個補發。這個邏輯我們封裝成一個send方法,如下:

当链接建立之后,发送消息的方法如下:<br /> `
登入後複製
二、demo功能解析

#########1、首頁entry######

為了簡化模型,把重點放在webSocket上,我們把首頁做成自己填寫房間號碼的形式。讀者如果自己有時間和能力的話,可以把首頁做成一個房間列表,並顯示每個房間有多少人在玩,只有一人的可以進去跟他玩。甚至後面還可以加上觀看模式,點擊別人的房間進去觀看別人怎麼玩。

微信小程式開發之websocket實例詳解

填入房間號碼的input元件,新增一個事件,取得它的值event.detail.value後setData到本page裡面。

點擊“開始遊戲”,再把房間號碼存入app的globalData裡面,然後wx.navigateTo到主遊戲頁面index。

這個頁面比較簡單。

2、主遊戲頁面

我們封裝一個websocket/connect.js模組,專門用來處理websocket連結。主要有兩個方法,connect發起webSocket鏈接,send用來發送資料。

index主頁:

微信小程式開發之websocket實例詳解

初始化狀態,9x9的格子,每一個格子其實都是button按鈕。我們產生的地圖場景數據,分別對應每一格。例如1表示週邊的1個金子,0表示週邊沒有金子,-1表示這格是個金子,我們的目標就是找到這些-1。找越多得分越高。

這裡討論一個安全性問題。相信一句話:在前端做的安全措施大都是不靠譜的。上圖中的矩陣,每個格子背後的數據,不應該放在前端,因為js程式碼是可以調試的,可以下斷點在相應的變數上,就可以看到整個矩陣數據,然後就知道哪些格子是金子,就可以作弊,這是非常不公平的。所以最好的方法是把這些矩陣資料存在後端,每次使用者操作的時候,把使用者點擊的座標發到後台,後台再判斷對應的座標是什麼數據,再回到前端。這個看似有很多資料傳輸的互動方式,其實是不會浪費資源,因為用戶的每個點擊操作,本來就要上報到後台,這樣遊戲的另一玩家才知道你點了哪個格子。反正都是要傳數據的,所以一定要傳座標,這樣前端就完全沒有必要知道哪個格子是什麼數據,因為後台的推播訊息會告訴你。

這樣我們就繞過了前端存矩陣資料的問題。但是我們還是需要一個數組來儲存當前矩陣狀態的,例如哪個格子已經被翻開,裡面是什麼數據,也就是說要儲存場上已經被打開的格子。所以在後台,我們要儲存兩個數據,一個是所有的矩陣數據,也就是地圖場景資料;另一個是目前狀態的數據,這個要用來同步雙方的介面。

3、結束頁面

遊戲結束的判斷條件,就是場上所​​有的金子都被挖完了。這個條件也是在後台判斷的。

在每次使用者挖到金子的時候,後台都會多一個判斷邏輯,就是看這個金子是否是最後一個。如果是的話,就發送一個over類型的訊息給遊戲的所有玩家。

當玩家終端接收到這個訊息時,就會結束目前的遊戲,並跳到結束頁面。

微信小程式開發之websocket實例詳解

没有专门的设计师,随便网上偷了张图片贴上去,界面比较丑。下方显示自己的得分和当前的房间号。

三、实现细节

1、代码结构

微信小程式開發之websocket實例詳解

前端代码,分了几个模块:pages放所有的页面,common放通用的模块,mime放挖金子的主逻辑(暂时没用到),res放资源文件,websocket放webSocket相关的处理逻辑。

后台代码,读者稍微了解一下就行了,不讨论太多。里面我放了docker文件,熟悉docker的读者可以直接一个命令跑起整个服务端。笔者在自己的服务器上跑了这个webSocket服务,ip和端口已经写在前端代码里,读者轻虐。可能放不久,读者可以自己把这个服务跑起来。

2、消息收发

(1)消息协议

我们简单地定义下,消息的格式如下。 发送消息:

`{type: &#39;dig&#39;, …}
登入後複製

服务器返回的消息:

{errCode: 0, data: {type: &#39;dig&#39;, …} }
登入後複製

因为webSocket类型的消息跟传统的http请求不太一样,http请求没有状态,一个请求过去,一会儿就返回,返回的数据肯定是针对这个请求的。而webSocket的模型是这样的:客户端发过去很多请求,然后也不知道服务器返回的数据哪个是对应哪个请求,所以需要一个字段来把所有的返回分成多种类型,并进行相应的处理。

(2)发送消息

发送消息就比较容易了,上面我们定义了一个send方法及未连接成功时的简单的消息列表。

(3)接收消息

读者在阅读代码的时候,可能会有一个疑惑,websocket/connect.js里只有send发送方法,而没有接收推送消息的处理,那接收消息的处理在哪?怎么关联起来的?

websocket/目录里面还有另一个文件,msgHandler.js,它就是用来处理接收消息的主要处理模块:

微信小程式開發之websocket實例詳解

从服务器推送过来的消息,主要有这三种类型:1挖金子操作,可能是自己的操作,也可能是对方的操作,里面有一个字段isMe来表示是否是自己的操作。接收到这类消息时,会翻转地图上相应的格子,并显示出挖的结果。2创建或进入房间的操作,一个房间有两个用户玩,创建者先开始。3游戏结束的消息,当应用接收到这类消息时,会直接跳转到结束页面。

这个处理逻辑,是在websocket/connect.js的wx.onSocketMessage回调里关联上的。

在消息的收发过程中,每个消息交互,调试工具都会记录下来。可以在调试工具里看到,在NetWork->WS里就可以看到:

微信小程式開發之websocket實例詳解

3、前端挖金子

代码如下:

var websocket = require(&#39;../../websocket/connect.js&#39;); var msgReceived = require(&#39;../../websocket/msgHandler.js&#39;);
Page({
    data: {
        mimeMap: null,
        leftGolds: 0, // 总共有多少金子 score: 0, // 我的得分 roomNo: 0 // 房间号 },
    x: 0, // 用户点中的列 y: 0, // 用户点中的行 onLoad: function () { var roomNo = app.getRoomNo(); this.setData({
            roomNo: roomNo
        }); // test // websocket.send(&#39;before connection&#39;); if (!websocket.socketOpened) { // setMsgReceiveCallback websocket.setReceiveCallback(msgReceived, this); // connect to the websocket websocket.connect();
            websocket.send({
              type: &#39;create&#39; });
        } else {
            websocket.send({
              type: &#39;create&#39;,
              no: roomNo
            });
        }
    },
    digGold: function(event) { // 不直接判断,而把坐标传给后台判断 // 被开过的就不管了 if (event.target.dataset.value < 9) { return;
        } // 取到这格的坐标 this.x = parseInt(event.target.dataset.x); this.y = parseInt(event.target.dataset.y); console.log(this.x, this.y); // 上报坐标 this.reportMyChoice();
    },
    reportMyChoice: function() {
        roomNo = app.getRoomNo();
        websocket.send({
            type: &#39;dig&#39;,
            x: this.x,
            y: this.y,
            no: roomNo
        });
    },
});
登入後複製

在page的onLoad事件里,先更新界面上的房间号信息。然后开始我们的重点,websocket.connect发起webSocket链接,websocket是我们封装的模块。然后把我们msgHandler.js处理逻辑设置到服务端推送消息回调里面。接着,发送一个create消息来创建或加入房间。服务端会对这个消息做出响应,返回本房间的地图场景数据。

digGold是每个格子的点击事件处理函数。这儿有一个逻辑,一个格子周边最多有8个格子,所以每个格子的数据最大不可能大于8,上面代码中可以看到有一个9,这其实是为了跟0区分,用来表示场上目前的还没被翻开的格子的数据,用9来表示,当然你也可以用10,100都行。

wxml的矩阵数据绑定代码如下:

`
<view wx:for="{{mimeMap}}" wx:for-item="row" wx:for-index="i"
class="flex-container">
<button wx:for="{{row}}" wx:for-item="cell" wx:for-index="j"
class="flex-item {{cell<0?&#39;gold&#39;:&#39;&#39;}} {{cell<9?&#39;open&#39;:&#39;&#39;}}"
bindtap="digGold" data-x="{{j}}" data-y="{{i}}" data-value="{{cell}}">
{{cell<9?(cell<0?&#39;*&#39;:cell):"-"}}
</button>
</view>
登入後複製

4、服务端实现

简单的提一下就好,因为后台不是本系列文章的重点,虽然这个demo的开发也花了大半的时候在写后台。前后端的消息交互,借助了webSocket通道,传输我们自己定义格式的内容。上面有个截图显示了后台代码目录的结构,划分得比较随意,handlers里存放了的是主要的处理逻辑。webSocketHandler是入口,在它的on_message里,对收到的客户端的消息,根据类型进行分发,dig类型,分发到answerHandler去处理,create类型,分发到roomHandler里去处理。

还有一点稍微提一下,本例子中的后台webSocket消息处理也跟传统的http处理流程有一点不一样。就是在最后返回的时候,不是直接返回的,而是广播的形式,把消息发送给所有的人。比如用户A点击了格子,后台收到坐标后,会把这个坐标及坐标里的数据一起发送给房间里的所有人,而不是单独返回给上报坐标的人。只是会有一个isMe字段来告诉客户端是否是自己的操作。

总之,在做webSocket开发的时候,上面提到的,前后端都可能会有一些地方跟传统的http接口开发不太一样。读者尝试在做webSocket项目的时候,转换一下思维。

最后提下一个注意点:微信小程序的websocket链接是全局只能有一个,官方提示:“ 一个微信小程序同时只能有一个 WebSocket 连接,如果当前已存在一个 WebSocket 连接,会自动关闭该连接,并重新创建一个 WebSocket 连接。 ”

以上是微信小程式開發之websocket實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!