ホーム < ゲームつくろー! < IKD備忘録

サーバ
サーバからのお知らせをクライアントで待つ


 クライアントからサーバに対して何か要求する事は前章でできました。これでゲームクライアント側から何かアクションを起こせます。でも、例えば将棋を想像してみるとこれだけではまずい事が分かります。将棋をするには2つのクライアントが必要です。それは用意できたとしていざ打とうとすると、先手の最初の一手を後手は「
待たないと」いけません。本当の将棋だったら、先手が将棋盤上のコマを移動させたのを見て後手が打てますが、2人が直接会話出来ない場合、誰かが仲介に入って後手に対して「先手が○○に打ったので後手の番ですよ〜」と教えてあげる必要があります。この仲介役がサーバである事は察しがつくと思います。でも、問題は後手はいつ先手が打ち終わるかわからないので、サーバから来るお知らせをず〜っと待たなければならないという点にあります。アクションを起こすきっかけがサーバにあるという事です。

 これはいわゆるクライアントに「リスナー(聴講者)」機能が必要となりますが、それをどう実装するのか?この章ではクライアントリスナーについて調べていく事にします。



@ Comet!

 Google先生に「JavaScript リスナー」等と尋ねてみたのですが、ん〜何だかうまくヒットしません。クライアント側でサーバからのお知らせを待つ…そういう仕組みを使っていそうな物と言うと…あ、「チャット」がありますね。チャットは誰かがサーバに呟きを送信したら、それをチャットに参加している全員に配ります。その時にクライアントはサーバからの連絡を待っているはずです。という事で「JavaScript チャット」で調べてみると…おっ、「Comet」というのが見つかりました(ITpro:第2回 Comet---プッシュ型のWebアプリケーションを作る )。これによると、サーバからクライアントに向けて送信する形式は「プッシュ型」と呼ばれているようです。一方でクライアント側がサーバにデータを送信(何か要求)する形式を「プル型」と言うようです。へぇ〜

 プッシュ型を実現するCometの原理はそれほど難しくはありません。クライアント側からサーバへ「何かあったら連絡して下さい」と
HTTPでPOSTをします。これは前章のXMLHttpRequest.openやsendメソッドで実現できます。違うのはここから。前章ではサーバが直ぐに応答を返しましたが、Cometの場合はそこで待機状態にしてしまいます。こうするとXMLHttpRequest.onreadystatechangeメソッドが反応待ち状態になります。後はサーバー側が何らかのきっかけでその待機状態を解除すると、クライアントに直ちに信号が届くというわけ。

 さっそくテストコードにチャレンジです。
 ん〜〜、ちっちゃなゲームを作ります。ゲームが始まると「位置について…」と表示されます。クライアント側が何かキーを押すと「よ〜い…」という状態になります。ここでサーバ側にComit要求です。サーバ側はランダムな時間だけクライアントを待たせて、あるタイミングで「ドン!」を返します。クライアントはそれを受けて素早くキーを押します!

 クライアント側に新技術は何もありません。ただ、サーバ側は「待つ」という新要素があります。今サーバはPHPで書いています。PHPで待つためには…調べましょう。あ、usleep関数というのがありました。今回のサンプルはこれで行きます。

 クライアント側は起動と同時に「位置について…」と表示、キー押し下げでサーバにComit要求すると同時に「よ〜い…」に描画を変え待機。XMLHttpRequest.onreadystatechangeメソッドに反応が戻ってきたら「ドン!」を表記すると同時にキー押し下げ関数を取り換えます。実装コードはこんな感じになるかな:

XMLHttpRequest.openメソッド
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>XMLHTTPRequestテスト</title>

        <script>
            var context;
            var curStr = "位置について…";
            var startTime = 0;
            var reactionTime = null;

            window.onload = function() {
                var canvas = document.getElementById("view");
                if ( canvas.getContext ) {
                    context = canvas.getContext("2d");
                    setInterval( mainLoop, 16 );

                    // 最初のキー押し下げ時でキーアップを受付ける
                    document.onkeydown = function() {
                        document.onkeyup = onKeyUp_Ready;
                        document.onkeydown = null;
                    }
                }
            }

            // 「よ〜い」にする
            function onKeyUp_Ready( e ) {
                document.onkeyup = null; // このメソッドがもう呼ばれないように対処
                curStr = "よ〜い…";

                // サーバに送信!
                var xmlHttpRequest = new XMLHttpRequest();
                xmlHttpRequest.open( "POST", "php/comit.php", true );

                xmlHttpRequest.onreadystatechange = function() {
                    // 受信!
                    if ( xmlHttpRequest.readyState == 4 )
                        // 「ドン!」に遷移
                        curStr = "ドン!";

                        // スタート時刻を取得
                        startTime = (new Date()).getTime();

                        // タイマーストップ受付け
                        document.onkeydown = onKeyDown_Stop;
                };

                xmlHttpRequest.send( null );
            }

            // 「ドン!」の後のタイマーストップ
            function onKeyDown_Stop( e ) {
                // 反応時間を取得
                reactionTime = (new Date()).getTime() - startTime;
                document.onkeydown = null;
            }

            function mainLoop() {
                context.clearRect( 0, 0, 640, 240 );
                context.font = "48px Arial";
                context.fillText( curStr, 20, 60 );
                if ( reactionTime ) {
                    context.font = "32px Arial";
                    context.fillText( "反応時間 " + reactionTime + " msec!", 20, 100 );
                }
            }
        </script>
    </head>

    <body>
        <canvas id="view" width=640 height=240></canvas>
    </body>
</html>

 クライアント側ではページが読まれた時(window.onload)にキー押し下げを受け付けるようにします。一度キーが押されたら、今度はキーアップ(onKeyDown_Ready)を登録します。これでキーを離すとサーバへ「ドン!」のタイミングの要求を出しに行くようになります。サーバでは適当な時間が経過したら反応を返すようにします。反応が来たらXMLHttpRequest.onreadystatechangeメソッドが呼ばれるので、ここで時間経過計測をスタートさせ、同時に押し下げ(onKeyDown_Stop)を再度有効にします。後は「ドン!」の表記に反応してキーが押されれば、onKeyDown_Stop関数が呼ばれるので、そこで時間経過を計算して画面に表示します。

 サーバ側はとっても簡単。呼ばれた時に適当な時間待つだけなので、こういう感じになりそうです:

XMLHttpRequest.openメソッド
<?php
    // 乱数時間だけ待つ
    usleep( mt_rand( 500, 6000 ) * 1000 );
?>

usleep関数はマイクロ秒(100万分の1秒単位)で待つ関数なので1000倍しています。

以上の仕組みだけで、サーバ側からプッシュされるスタイルが実現できます:


テストはこちら

 さて、これでクライアントが待ってサーバが任意のタイミングでクライアントに反応を返す所までできました。ただ、これはクライアント一人に対してのみの作用です。例えば将棋のような相手がいるゲームの場合、サーバはどちらかのクライアントが手を進めたら「対戦相手」にそれを伝える必要があります。という事はサーバは双方の存在を覚えておく必要があるわけです。これ、ちょっと大変な予感がします(^-^;