ホームページ > 記事 > WeChat アプレット > WeChat アプレット開発用の WebSocket サンプルの詳細な説明
WebSocket が必要な理由は何ですか?
従来のリアルタイム インタラクティブ ゲーム、または積極的にメッセージを送信するサーバーの動作 (プッシュ サービスなど) を WeChat 上で実行したい場合は、ポーリングを使用できますが、これは大量のリソースを消費し、大量のリソースを必要としますリクエストの増加によりサーバーの負荷も増加し、遅延の問題が深刻になります。自分で開発したアプリの場合、これらの問題を解決するために、多くのチームは独自のソケットを構築し、TCP ロングリンクとカスタム プロトコルを使用して、比較的リアルタイムのデータでサーバーと対話します。有能なチームであれば、このアプローチを採用することに大きな問題はありません。ただし、小規模なチームでは、多くの問題のデバッグと解決に多くの時間を費やす必要がある場合があり、コストの観点からは価値がありません。
H5 では、Web ページ上の長いリンクの問題を解決するために webSocket が導入され、WeChat アプレットも WebSocket をサポートします。これは非常に重要な機能であるため、このシリーズの記事では 1 つの記事を WebSocket について説明することにします。
WebSocket は本質的には、全二重データ送信を提供する TCP 接続です。ポーリングによる頻繁な接続の確立と切断によるパフォーマンスの低下を回避できる一方で、(長いリンクであるため) データを双方向にリアルタイムで送信でき、WebSocket は相互接続を可能にします。 - ドメイン通信 (クロスドメインのセキュリティ問題はサーバーによって解決される必要がある可能性があります)。現在、IE 以外のブラウザはすでに webSocket を十分にサポートしています。WeChat アプレットが再び開始されると、WebSocket はさらに普及するでしょう。
新しいデモ、より興味深いミニゲーム、マインスイーパーのマルチプレイヤー バージョン、または正確に言うと、金採掘のマルチプレイヤー バージョンをデザインしましょう。
ゲームのルールは次のとおりです。金を掘ると、各人が1ポイントを獲得します(掘った後、AはBに変わり、BはAを掘った後にもう一度クリックできます)。 ) ゴールドをクリックしても、それは爆発せず、フィールド上のすべてのゴールドが採掘されるまでゲームは終了しません。マインスイーパと同様に、数字は周囲に金がいくつあるかを示し、ユーザーはフィールド上に表示された数字に基づいてどのマスに金が含まれているかを推測できます。
このインタラクティブ ゲームの難しさは、ユーザーのクリック操作がサーバーに送信され、サーバーがそれを他のプレイヤーのアプリケーションにリアルタイムでプッシュする必要があることです。また、ユーザー自身も、同じグリッドを何度もクリックしないように、相手の操作中にリアルタイムで送信されるデータを受信する必要があります。簡単に言うと、操作をサーバーに報告する必要があり、サーバーはリアルタイムでメッセージをプッシュする必要があります。モデル全体を簡素化するために、プレーヤー A がクリックを完了した後、プレーヤー B の順番でクリックする必要があると規定します。プレーヤー B が操作を完了した後、プレーヤー A がクリックできるようになります。
この機能はいくつかの手順で実装します。
1. 最初のステップは、掃海マップ シーンを生成することです
このアルゴリズムは比較的単純なので、簡単に説明しましょう。特定の行または列をランダムに選択することで、グリッドを見つけて金色としてマークすることができます (-1 は金色を意味します)。 mimeCnt は生成される金の量を表し、同様に mimeCnt ランダム グリッドを周期的にマークします。生成が完了したら、ループを使用してこれらの -1 グリッドをスキャンし、その周囲のグリッドに 1 を追加します。 もちろん、1 を追加するには、それらのグリッドが非ゴールド グリッドである必要があります。コードはここにあります。
increaseArround は、このゴールド グリッドの周囲のすべてのグリッドに 1 を加算するために使用されます。実装は比較的単純です。
genMimeArr() を実行すると、ランダムに生成された結果は次のようになります。
-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, });
在调用这个请求代码之前,先添加下事件监听,这样才知道有没有连接成功:ff9d32c555bb1d9133a29eb4371c1213 `
wx.onSocketOpen(function(res){ console.log('websocket opened.'); });
`连接失败的事件:
wx.onSocketError(function(res){ console.log('websocket fail'); }) `
收到服务器的消息时触发的事件:
` wx.onSocketMessage(function(res){ console.log('received msg: ' + res.data); })
当链接建立之后,发送消息的方法如下:ff9d32c555bb1d9133a29eb4371c1213 `
消息发送
由于建立链接是需要几次握手,需要一定的时间,所以在wx.connectSocket成功之前,如果直接wx.sendSocketMessage发送消息会报错,这里做一个兼容,如果连接还没建立成功,则用一个数组来保存要发送的信息;而链接第一次建立时,把数据遍历一遍,把消息拿出来一个个补发。这个逻辑我们封装成一个send方法,如下:
` function sendSocketMessage(msg) { if (typeof(msg) === 'object') { // 只能发送string msg = JSON.stringify(msg); } if (socketOpened) { // socketOpened变量在wx.onSocketOpen时设置为true wx.sendSocketMessage({ data:msg }); } else { // 发送的时候,链接还没建立 socketMsgQueue.push(msg); } }
1、首页entry
モデルを簡素化し、webSocket に焦点を当てるために、ホームページを部屋番号を自分で入力するフォームにしました。読者に時間と能力がある場合は、ホームページに部屋のリストを作成し、各部屋で何人がプレイしているかを表示することができます。1 人しかいない人は、その部屋に入って一緒にプレイすることができます。後で表示モードを追加することもでき、他の人のルームをクリックして他の人がどのようにプレイするかを観察することもできます。
部屋番号の入力コンポーネントを入力し、イベントを追加し、その値event.detail.valueを取得して、このページにDataを設定します。
「ゲーム開始」をクリックし、アプリの globalData に部屋番号を保存し、wx.navigateTo でメイン ゲーム ページのインデックスに移動します。
このページは比較的シンプルです。
2. メイン ゲーム ページ
WebSocket リンクを処理するために特別に使用される websocket/connect.js モジュールをカプセル化します。主なメソッドは 2 つあり、connect は webSocket リンクを開始し、send はデータの送信に使用されます。
index メイン ページ:
初期化状態、9x9 グリッド、各グリッドは実際にはボタンです。生成したマップシーンデータは各グリッドに対応しています。たとえば、1 は周囲に金が 1 つあることを意味し、0 は周囲に金が存在しないことを意味し、-1 はこのグリッドが金であることを意味します。私たちの目標は、これらの -1 を見つけることです。たくさん見つければ見つけるほど、スコアは高くなります。
ここではセキュリティの問題について説明します。信じられないかもしれませんが、フロントエンドで行われるセキュリティ対策のほとんどは信頼できません。上の図の行列 (各グリッドの背後にあるデータ) はフロントエンドに配置すべきではありません。これは、JS コードをデバッグできるため、対応する変数にブレークポイントを設定でき、行列データ全体を確認して知ることができます。どのグリッド あなたがゴールドであれば、不正行為ができます。これは非常に不公平です。したがって、これらの行列データをバックエンドに保存するのが最善の方法です。ユーザーが操作するたびに、ユーザーがクリックした座標がバックエンドに送信され、対応する座標がどのようなデータであるかを判断してフロントエンドに返します。このインタラクティブな方法は大量のデータ送信を伴うように見えますが、実際にはリソースを無駄にしません。ユーザーのすべてのクリック操作がバックグラウンドに報告される必要があるため、ゲーム内の他のプレイヤーはどのグリッドをクリックしたかを知ることができます。いずれにしてもデータを送信する必要があるため、座標も送信する必要があります。この方法では、バックグラウンドのプッシュ メッセージが通知するため、フロントエンドはどのグリッドにどのようなデータが含まれているかを知る必要がありません。
このようにして、フロントエンドで行列データを保存する問題を回避します。ただし、どのグリッドが開かれているか、その中にどのようなデータがあるかなど、現在の行列の状態を保存する配列がまだ必要です。つまり、開かれているフィールド上のグリッドを保存する必要があります。したがって、バックグラウンドで 2 つのデータを保存する必要があります。1 つはすべてのマトリックス データ、つまりマップ シーン データであり、もう 1 つは現在のステータス データであり、双方のインターフェイスを同期するために使用されます。
3. 終了ページ
ゲームの終了を判断する条件は、フィールド上のすべてのゴールドが採掘されることです。この状態もバックグラウンドで判断されます。
ユーザーがゴールドを掘るたびに、バックグラウンドで追加の判断ロジックが実行され、ゴールドが最後のものであるかどうかが確認されます。その場合は、ゲーム内のすべてのプレイヤーにオーバータイプ メッセージを送信します。
プレイヤー端末がこのメッセージを受信すると、現在のゲームを終了し、終了ページにジャンプします。
没有专门的设计师,随便网上偷了张图片贴上去,界面比较丑。下方显示自己的得分和当前的房间号。
1、代码结构
前端代码,分了几个模块:pages放所有的页面,common放通用的模块,mime放挖金子的主逻辑(暂时没用到),res放资源文件,websocket放webSocket相关的处理逻辑。
后台代码,读者稍微了解一下就行了,不讨论太多。里面我放了docker文件,熟悉docker的读者可以直接一个命令跑起整个服务端。笔者在自己的服务器上跑了这个webSocket服务,ip和端口已经写在前端代码里,读者轻虐。可能放不久,读者可以自己把这个服务跑起来。
2、消息收发
(1)消息协议
我们简单地定义下,消息的格式如下。 发送消息:
`{type: 'dig', …}
服务器返回的消息:
{errCode: 0, data: {type: 'dig', …} }
因为webSocket类型的消息跟传统的http请求不太一样,http请求没有状态,一个请求过去,一会儿就返回,返回的数据肯定是针对这个请求的。而webSocket的模型是这样的:客户端发过去很多请求,然后也不知道服务器返回的数据哪个是对应哪个请求,所以需要一个字段来把所有的返回分成多种类型,并进行相应的处理。
(2)发送消息
发送消息就比较容易了,上面我们定义了一个send方法及未连接成功时的简单的消息列表。
(3)接收消息
读者在阅读代码的时候,可能会有一个疑惑,websocket/connect.js里只有send发送方法,而没有接收推送消息的处理,那接收消息的处理在哪?怎么关联起来的?
websocket/目录里面还有另一个文件,msgHandler.js,它就是用来处理接收消息的主要处理模块:
从服务器推送过来的消息,主要有这三种类型:1挖金子操作,可能是自己的操作,也可能是对方的操作,里面有一个字段isMe来表示是否是自己的操作。接收到这类消息时,会翻转地图上相应的格子,并显示出挖的结果。2创建或进入房间的操作,一个房间有两个用户玩,创建者先开始。3游戏结束的消息,当应用接收到这类消息时,会直接跳转到结束页面。
这个处理逻辑,是在websocket/connect.js的wx.onSocketMessage回调里关联上的。
在消息的收发过程中,每个消息交互,调试工具都会记录下来。可以在调试工具里看到,在NetWork->WS里就可以看到:
3、前端挖金子
代码如下:
var websocket = require('../../websocket/connect.js'); var msgReceived = require('../../websocket/msgHandler.js'); 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('before connection'); if (!websocket.socketOpened) { // setMsgReceiveCallback websocket.setReceiveCallback(msgReceived, this); // connect to the websocket websocket.connect(); websocket.send({ type: 'create' }); } else { websocket.send({ type: 'create', 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: 'dig', 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?'gold':''}} {{cell<9?'open':''}}" bindtap="digGold" data-x="{{j}}" data-y="{{i}}" data-value="{{cell}}"> {{cell<9?(cell<0?'*':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 连接。 ”
以上がWeChat アプレット開発用の WebSocket サンプルの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。