
ライブプロジェクトでこれを試す時が来たと思いました。 そして今、私はそれから来たものを共有しています。
タスクの本質
私個人の小さなサイトKlavogonki.ruには、中心的な部分があります-現在アクティブな現在のゲームレースのリストです。 リストは非常に動的です。プレイヤーは数秒ごとに、時には1秒あたり数回、新しいレースを作成します。 到着はカウントダウンで始まり、完了すると、オープンゲームセクションからアクティブセクションに移動します。 すべてのプレイヤーが終了すると、レースはページから削除されます。 1人から、時には最大100人のプレイヤーまで、1レースが必要です。このレースをすぐに表示する必要があります。

以前の仕組み
当初、サイトの機能のこの部分を作成する必要が生じたとき、リストの動的な更新には多くの困難が生じました。 リストページには、一度に数十人のユーザーを含めることができ、各ユーザーは新鮮な写真を見たいと思っています。 多くのレースでは、作成から開始までのカウントダウンのタイムアウトをわずか10〜20秒で設定できます。また、レースに参加する時間を確保するために、更新は非常に活発でなければなりません。
タイムアウトでページ全体を更新することは、ここではまったく当てはまらず、他のオプションを探す必要がありました(ここでは、非常に強いニーズがない限り、サイトでフラッシュを使用したくないことに注意する必要がありました)。
ここで最も明白で最も簡単な解決策は、一見、 長いポーリング -サーバーへの接続がハングしているように見えました。これは、新しいイベントが到着して再び開くと切断されます。 ただし、いくつかのテストの後、このオプションは実行不可能であることが判明しました。新しいゲームの作成だけでなく、すべてのゲームのパラメーターの変更(タイムアウトの開始、ステータスの変更、プレーヤーの構成など)、および開始されたリクエストの数についてクライアントに通知する必要があるため、イベントは連続ストリームで受信されましたサーバーにある程度の不満を引き起こします。 また、開閉リクエストのオーバーヘッドもかなり大きくなりました。
多くのユーザーのプロキシサーバーに問題があるため、HTTPストリーミングを使用できませんでした。
そのため、ajaxリクエストで3秒ごとにタイムアウトすることでページのコンテンツを更新する単純なバージョンに決めました。 サーバーでは、現在のデータがキャッシュされ、jsonのキャッシュからクライアントに送信されましたが、トラフィックを節約するために、リスト全体が毎回与えられるのではなく、バージョン管理システムを介して変更されたデータのみが要求されました(要求されたものと比較してバージョンが増加しました-到着に関する新しい情報を提供しますバージョン番号)。
システムは良好に機能し、長時間動作しました。 しかし、大きなマイナスがありました-開始前に10秒のタイムアウトでレースに参加することは非常に困難です。 さらに、ダイナミックなオンラインレースゲームの精神にまったく対応しておらず、一般的にはあまり技術的に見えませんでした。
このリンクから古いバージョンのこのページを見ることができます。
今どのように機能しますか
要するに、Webソケットはプロセス全体を駆動することを可能にしました。
まず、現在のゲームバックエンドと連動するサーバーを選択しました。 いくつかの理由から、このためにnode.jsを選択しました。イベント駆動型モデルとよく開発されたjavascriptコールバックは、このタスクに最適でした。
PHPバックエンドとnode.js上のサーバー間の一般的な通信環境は、 redis pubsubチャンネルです。 新しいゲームやデータを変更するアクションを作成する場合、phpは次のようなことを行います(ここと以降のコードは大幅に簡略化されています)。
$redis = new Redis(); $redis->pconnect('localhost', 6379); $redis->publish("gamelist", json_encode(array( "game created", array( 'gameId' => $id))));
Redisは、個別のTCPポートで個別のデーモンとして動作し、接続されている任意の数のクライアントからメッセージを送受信します。 これにより、プロセスの数(楽観的な場合はサーバー)phpおよびnode.jsに関係なく、システムを適切にスケーリングできます。 現在、約50個のphpプロセスと2個のnode.jsプロセスが回転しています。
node.js側では、起動時にgamelistと呼ばれる
gamelist
-channel
gamelist
への接続があります:
var redis = require('redis').createClient(6379, 'localhost'), redis.subscribe('gamelist');
クライアントと連携するために、 Socket.IOバインディングライブラリが使用されます( upd:同志のVoenniyとJoesは、 SockJSやBesedaなどのより良い代替手段があると言っていますが、これは本当かもしれません)。 ブラウザがWebソケットをサポートしていない場合、フラッシュやxhrポーリングなどの他のトランスポートにロールバックしながら、Webソケットをメイントランスポートとして使用できます。 さらに、一般的なクライアントとの作業を簡素化します。たとえば、接続されたクライアントを多重化して異なる疑似ディレクトリ(チャネル)に分離するAPIを提供し、イベントやその他のグッズに名前を付けることができます。
var io = require('socket.io').listen(80); var gamelistSockets = io.of('/gamelist');
クライアントブラウザが
ws://ws.klavogonki.ru/gamelist
に接続されている場合、gamelist socketioチャネルに接続されていると認識されます。 ブラウザはこれについて次のことを行います。
<script src="http://ws.klavogonki.ru/socket.io/socket.io.js" type="text/javascript"></script> ... <script type="text/javascript"> var socket = io.connect('ws.klavogonki.ru/gamelist'); </script>
イベントがredisチャネル経由でバックエンドから到着すると、イベントは事前に完全に分析されてから、
gamelistSockets
接続されているすべてのクライアントに送信されます。
redis.on('message', function(channel, rawMsgData) { if(channel == 'gamelist') { var msgData = JSON.parse(rawMsgData); var msgName = msgData[0]; var msgArgs = msgData[1]; switch(msgName) { case 'game created': { ... gamelistSockets.emit('game created', info); break; } case 'game updated': { ... gamelistSockets.emit('game updated', info); break; } case 'player updated': { ... gamelistSockets.emit('player updated', info); break; } } } });
ブラウザーはまったく同じ方法でイベントを受信し、必要な変更をページにレンダリングします。
socket.on('game created', function(data) { insertGame(data); }); socket.on('game updated', function(data) { updateGame(data); }); socket.on('player updated', function(data) { updatePlayer(data); });
原則は完全にシンプルで明確です。 このスキームの中心にある高度な技術により、プロセスが大幅に簡素化され、作業自体のロジックに集中できます。 「ステータスではなく変更を報告する」というイデオロギーで動作するようにPHPコードの一部を変更する必要がありましたが、websocketのドメインをメインマシンとは別のドメインに移動しました(ポート80で共有プロキシに苦しむことがないように)乾燥残渣プラスは非常に重要でした:
- 最高の動的インターフェースである更新はリアルタイムで行われ、90年代のチャットページではなく、オンラインゲームで1つの変更を追跡して感じることができます。
- データはバックエンドからブラウザに直接転送されるため、キャッシングの必要はほとんどありません。
- 必要な状態変更のみを送信することによるオーガニックなトラフィックの節約(圧縮を強化しようとすると、さらに興味深いものになります)。
- node.jsは、考えられる数の同時接続を保持および処理するためだけに開発されたため、ネットワーク負荷の増大はほとんど感知できません。 状態変化の計算はバックエンドで1回行われ、既製のすべての顧客に送信されるため、CPUの負荷も低下しました。
- イベント指向のスキームにより、データ変更のすべての瞬間について知ることができ、たとえば、フローティングとフローティングのアニメーションを同時に作成することができます。
要するに堅実な利益。
ここで最後に起こったことを見ることができます。 違いは肉眼で見ることができます。
ボーナスとして、2つのタブレット、Klavogonokの視聴者に関する統計、Socket.IOで使用されるブラウザとトランスポートがあります。
ブラウザ | 共有する | 輸送 | 共有する |
---|---|---|---|
クロム | 51% | ウェブソケット | 90% |
Firefox | 20% | xhrポーリング | 5% |
オペラ | 15% | フラッシュソケット | 4% |
IE(約8と9の半分) | 6% | jsonppolling | 1% |
まとめ
結果、参考文献、および道徳を含む最終要約部分があります。 しかし、私はあなたの時間を節約し、簡単に言います: Webソケットはとてもクールです!
PSプロジェクトの新しく開発された部分(上記の部分を含む)では、 mongodbやangular.jsなどの興味深い単語も使用されます。 関心がある場合は、このトピックに関する次のトピックがあります。