web-dev-qa-db-ja.com

Facebook、Gmailはどのようにリアルタイム通知を送信しますか?

私はこのトピックに関するいくつかの投稿を読みましたが、答えはコメット、リバースajax、httpストリーミング、サーバープッシュなどです。

Gmailの受信メール通知はどのように機能しますか?

GMail Chatは、クライアントとの対話なしでAJAXリクエストを行うことができますか?

非常に簡単な例を書くために私が従うことができるコード参照があるかどうか知りたいです。多くの投稿やウェブサイトは、テクノロジーについて話しているだけです。完全なサンプルコードを見つけるのは困難です。また、彗星を実装するために多くの方法を使用できるようです。非表示のIFrame、XMLHttpRequest。私の意見では、XMLHttpRequestを使用する方が良い選択です。さまざまな方法の長所と短所をどう思いますか? Gmailはどちらを使用しますか?

サーバー側とクライアント側の両方で行う必要があることは知っています。 PHPおよびJavascriptのサンプルコードはありますか?

261
Billy

Facebookがこれを行う方法は非常に興味深いです。

このような通知を行う一般的な方法は、特定の間隔(おそらく数秒ごと)でサーバー上のスクリプトを(AJAXを使用して)ポーリングし、何かが発生したかどうかを確認することです。ただし、これはかなりネットワーク集約型であり、何も起こらなかったために無意味なリクエストを行うことがよくあります。

Facebookが行う方法は、ある間隔でポーリングするのではなく、コメットアプローチを使用しており、1つのポーリングが完了するとすぐに別のポーリングを発行します。ただし、サーバー上のスクリプトへの各要求には非常に長いタイムアウトがあり、サーバーは何かが発生した場合にのみ要求に応答します。 FacebookでFirebugの[コンソール]タブを表示すると、スクリプトへのリクエストに数分かかる場合があります。この方法は、リクエストの数と送信頻度の両方を即座に削減するため、非常に独創的です。これで、サーバーがイベントを「起動」できるイベントフレームワークが事実上作成されました。

その背後にあるのは、それらの投票から返された実際のコンテンツに関して、イベントのリストと思われるものとそれらに関する情報を含むJSON応答です。しかし、それは縮小されているので、少し読みにくいです。

実際のテクノロジーに関しては、AJAXがここに行く方法です。リクエストのタイムアウトなどを制御できるためです。 jQueryを使用してAJAXを実行することをお勧めします(ここではスタックオーバーフローの決まり文句)。クロス互換性の問題の多くが取り除かれます。 PHPに関しては、PHPスクリプトでイベントログデータベーステーブルをポーリングし、何かが発生した場合にのみクライアントに戻ることができますか?これを実装する方法はたくさんあります。

実装:

サーバ側:

PHPには彗星ライブラリの実装がいくつかあるように見えますが、正直なところ、実際には非常に単純で、次のような擬似コードのようなものです。

while(!has_event_happened()) {
   sleep(5);
}

echo json_encode(get_events());
  • Has_event_happened関数は、イベントテーブルなどで何かが発生したかどうかを確認し、get_events関数はテーブルの新しい行のリストを返しますか?本当に問題のコンテキストに依存します。

  • PHPの最大実行時間を変更することを忘れないでください。そうしないと、タイムアウトが早くなります!

クライアント側:

Cometインタラクションを行うためのjQueryプラグインをご覧ください。

とはいえ、プラグインはかなり複雑になっているように見えますが、クライアント上では非常に単純です。おそらく(jQueryを使用して)次のようになります。

function doPoll() {
   $.get("events.php", {}, function(result) {
      $.each(result.events, function(event) { //iterate over the events
          //do something with your event
      });
      doPoll(); 
      //this effectively causes the poll to run again as
      //soon as the response comes back
   }, 'json'); 
}

$(document).ready(function() {
    $.ajaxSetup({
       timeout: 1000*60//set a global AJAX timeout of a minute
    });
    doPoll(); // do the first poll
});

全体は、既存のアーキテクチャがどのように組み立てられるかに大きく依存します。

421
Kazar

更新

私はこれについて賛成票を受け取り続けているので、この答えは4歳であることを覚えておくのが合理的だと思います。 Webは非常に速いペースで成長しているので、この答えに注意してください。


私は最近同じ問題を抱えており、このテーマについて調査しました。

指定された解決策はロングポーリングと呼ばれ、それを正しく使用するには、AJAXリクエストのタイムアウトが「大きい」ことを確認し、現在の終了(タイムアウト、エラー、成功)後に常にこのリクエストを行う必要があります。

ロングポーリング-クライアント

ここでは、コードを短くするために、jQueryを使用します。

function pollTask() { 

    $.ajax({

        url: '/api/Polling',
        async: true,            // by default, it's async, but...
        dataType: 'json',       // or the dataType you are working with
        timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
        cache: false

    }).done(function (eventList) {  

       // Handle your data here
       var data;
       for (var eventName in eventList) {

            data = eventList[eventName];
            dispatcher.handle(eventName, data); // handle the `eventName` with `data`

       }

    }).always(pollTask);

}

jQuery docs から)を覚えておくことが重要です:

JQuery 1.4.x以前では、リクエストがタイムアウトした場合、XMLHttpRequestオブジェクトは無効な状態になります。オブジェクトメンバにアクセスすると、例外がスローされる場合があります。 Firefox 3.0以降でのみ、スクリプトおよびJSONPリクエストはタイムアウトによってキャンセルできません。タイムアウト期間が過ぎてもスクリプトは実行されます。

ロングポーリング-サーバー

特定の言語ではありませんが、次のようになります。

function handleRequest () {  

     while (!anythingHappened() || hasTimedOut()) { sleep(2); }

     return events();

} 

ここで、hasTimedOutはコードが永遠に待機しないことを確認し、anythingHappenedはイベントが発生したかどうかを確認します。 sleepは、何も起こらない間にスレッドを解放して他のことを行うためのものです。 eventsは、イベントのディクショナリ(または他の任意のデータ構造)をJSON形式(または任意の他のデータ構造)で返します。

それは確かに問題を解決しますが、研究しているときのようにスケーラビリティとパフォーマンスに懸念がある場合は、私が見つけた別のソリューションを検討するかもしれません。

解決

ソケットを使用してください!

クライアント側では、互換性の問題を回避するために、 socket.io を使用します。ソケットを直接使用しようとし、ソケットが利用できない場合は他のソリューションへのフォールバックがあります。

サーバー側で、NodeJSを使用してサーバーを作成します(例 here )。クライアントは、サーバーで作成されたこのチャンネル(オブザーバー)にサブスクライブします。通知を送信する必要がある場合は常に、このチャネルで通知が発行され、サブスクリプト(クライアント)に通知されます。

このソリューションが気に入らない場合は、APE( Ajax Push Engine )を試してください。

私が助けてくれたことを願っています。

39

Facebookのメッセージングシステムに関するスライドショー によると、Facebookは彗星技術を使用してWebブラウザにメッセージを「プッシュ」します。 Facebookのコメットサーバーは、オープンソースのErlang Webサーバーmochiweb上に構築されています。

以下の図では、「チャネルクラスター」という語句は「彗星サーバー」を意味します。

System overview

他の多くの大きなWebサイトでは、すべての企業のニーズに違いがあるため、独自のコメットサーバーを構築しています。しかし、オープンソースの彗星サーバー上に独自の彗星サーバーを構築することは良いアプローチです。

icomet 、libeventで構築されたC1000K C++コメットサーバーを試すことができます。 icometはJavaScriptライブラリも提供します。次のように簡単に使用できます。

var comet = new iComet({
    sign_url: 'http://' + app_Host + '/sign?obj=' + obj,
    sub_url: 'http://' + icomet_Host + '/sub',
    callback: function(msg){
        // on server Push
        alert(msg.content);
    }
});

icometは、Safari(iOS、Mac)、IE(Windows)、Firefox、Chromeなどを含む幅広いブラウザーとOSをサポートしています。

18
ideawu

FacebookはHTTPの代わりにMQTTを使用します。プッシュはポーリングよりも優れています。 HTTPを介してサーバーを継続的にポーリングする必要がありますが、MQTTサーバーを介してクライアントにメッセージをプッシュします。

MQTTとHTTPの比較: http://www.youtube.com/watch?v=-KNPXPmx88E

注:私の答えはモバイルデバイスに最適です。

5
abhinav

長いポーリングの重要な問題の1つは、エラー処理です。エラーには2つのタイプがあります。

  1. 要求がタイムアウトする場合があります。その場合、クライアントはすぐに接続を再確立する必要があります。これは、メッセージが到着していない場合の長いポーリングの通常のイベントです。

  2. ネットワークエラーまたは実行エラー。これは実際のエラーであり、クライアントはこれを正常に受け入れて、サーバーがオンラインに戻るまで待機する必要があります。

主な問題は、タイプ2エラーに対してもエラーハンドラーがすぐに接続を再確立すると、クライアントがサーバーをDOSすることです。

コードサンプルの両方の回答では、これを見逃しています。

function longPoll() { 
        var shouldDelay = false;

        $.ajax({
            url: 'poll.php',
            async: true,            // by default, it's async, but...
            dataType: 'json',       // or the dataType you are working with
            timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
            cache: false

        }).done(function (data, textStatus, jqXHR) {
             // do something with data...

        }).fail(function (jqXHR, textStatus, errorThrown ) {
            shouldDelay = textStatus !== "timeout";

        }).always(function() {
            // in case of network error. throttle otherwise we DOS ourselves. If it was a timeout, its normal operation. go again.
            var delay = shouldDelay ? 10000: 0;
            window.setTimeout(longPoll, delay);
        });
}
longPoll(); //fire first handler
5
Ronenz