web-dev-qa-db-ja.com

基本的な "Long Polling"を実装するにはどうすればいいですか?

Long Pollingがどのように機能するかについては多くの情報があります(たとえば、 this 、および this )。ただし、これをコードに実装する方法のsimpleの例はありません。

私が見つけることができるのは、Dojo JSフレームワークとかなり複雑なサーバーシステムに依存している cometd だけです。

基本的に、どのようにしてリクエストを処理するためにApacheを使うのでしょうか?そして、新しいメッセージを得るためにサーバを "長時間ポーリング"する簡単なスクリプト(例えばPHP)を書くにはどうすればいいですか?

この例は、スケーラブル、安全、完全である必要はありません。動作する必要があるだけです。

760
dbr

それは私が最初に考えていたより簡単です。基本的に、あなたが送りたいデータが利用可能になるまで(例えば、新しいメッセージが到着するまで)何もしないページがあります。

これは2〜10秒後に単純な文字列を送信する本当に基本的な例です。 404エラーを返す可能性が3分の1(今後のJavascriptの例ではエラー処理を示すため)

msgsrv.php

<?php
if(Rand(1,3) == 1){
    /* Fake an error */
    header("HTTP/1.0 404 Not Found");
    die();
}

/* Send a string after a random number of seconds (2-10) */
sleep(Rand(2,10));
echo("Hi! Have a random number: " . Rand(1,10));
?>

注:実際のサイトでは、Apacheのような通常のWebサーバーでこれを実行すると、すぐにすべての "ワーカースレッド"が縛られ、他の要求に応答できなくなります。これを回避する方法があります。 Pythonの twisted のような "ロングポーリングサーバー"。リクエストごとに1つのスレッドに依存しません。 cometD は人気のあるもので(いくつかの言語で利用可能です)、 Tornado はそのようなタスクのために特別に作られた新しいフレームワークです(FriendFeedのロングポーリングコード用に構築されました)。簡単な例では、Apacheは十分すぎるほどです。このスクリプトはどんな言語でも簡単に書くことができます(私はApache/PHPを非常に一般的なものとして選びました、そして私はたまたまそれらをローカルで実行していました)

次に、Javascriptでは、上記のファイル(msg_srv.php)を要求して応答を待ちます。入手すると、データに基づいて行動します。それからあなたはファイルを要求し、もう一度待って、データに基づいて行動します

以下はそのようなページの例です。ページがロードされると、msgsrv.phpファイルに対する最初の要求が送信されます。成功すると、メッセージを#messages divに追加し、1秒後にwaitForMsg関数を呼び出します。これもまた待機を引き起こします。

1秒のsetTimeout()は本当に基本的なレートリミッターですが、これなしでもうまくいきますが、msgsrv.php always が即座に返されると(構文エラーなどで) - ブラウザがあふれてすぐにフリーズすることがあります。 。これは、ファイルに有効なJSONレスポンスが含まれているかどうかをチェックすること、および/または1分あたり1秒あたりの合計リクエスト数を維持して適切に一時停止することをお勧めします。

ページにエラーがある場合は、エラーを#messages divに追加し、15秒待ってから再試行します(各メッセージの後に1秒待つ方法と同じ)。

このアプローチのいいところは、非常に回復力があるということです。クライアントのインターネット接続が切断された場合、タイムアウトしてから再試行します - これはポーリングがどのくらいの期間行われるかに固有のものです。複雑なエラー処理は必要ありません。

とにかく、jQueryフレームワークを使ったlong_poller.htmコード:

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    waitForMsg, /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    waitForMsg, /* Try again after.. */
                    15000); /* milliseconds (15seconds) */
            }
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>
502
dbr

slosh の一部として、本当に簡単なチャットの例があります。

編集 :(誰もがここに自分のコードを貼り付けているので)

これはロングポーリングと slosh を使用した完全なJSONベースのマルチユーザーチャットです。これは、呼び出し方法の demo です。XSSの問題は無視してください。最初にサニタイズせずにこれをデプロイしてはいけません。

クライアントalwaysがサーバーに接続していることに注意してください。誰かがメッセージを送信するとすぐに、だれもがすぐにそれを見るはずです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2008 Dustin Sallings <[email protected]> -->
<html lang="en">
  <head>
    <title>slosh chat</title>
    <script type="text/javascript"
      src="http://code.jquery.com/jquery-latest.js"></script>
    <link title="Default" rel="stylesheet" media="screen" href="style.css" />
  </head>

  <body>
    <h1>Welcome to Slosh Chat</h1>

    <div id="messages">
      <div>
        <span class="from">First!:</span>
        <span class="msg">Welcome to chat. Please don't hurt each other.</span>
      </div>
    </div>

    <form method="post" action="#">
      <div>Nick: <input id='from' type="text" name="from"/></div>
      <div>Message:</div>
      <div><textarea id='msg' name="msg"></textarea></div>
      <div><input type="submit" value="Say it" id="submit"/></div>
    </form>

    <script type="text/javascript">
      function gotData(json, st) {
        var msgs=$('#messages');
        $.each(json.res, function(idx, p) {
          var from = p.from[0]
          var msg = p.msg[0]
          msgs.append("<div><span class='from'>" + from + ":</span>" +
            " <span class='msg'>" + msg + "</span></div>");
        });
        // The jQuery wrapped msgs above does not work here.
        var msgs=document.getElementById("messages");
        msgs.scrollTop = msgs.scrollHeight;
      }

      function getNewComments() {
        $.getJSON('/topics/chat.json', gotData);
      }

      $(document).ready(function() {
        $(document).ajaxStop(getNewComments);
        $("form").submit(function() {
          $.post('/topics/chat', $('form').serialize());
          return false;
        });
        getNewComments();
      });
    </script>
  </body>
</html>
41
Dustin

Tornado はロングポーリング用に設計されており、サーバーコードとJSクライアントコードを含む非常に最小限の(数百行のPython) chatアプリ in/examples/chatdemoを含みます。それはこのように動作します:

  • クライアントはJSを使用して(最後のメッセージ数)以降の更新を要求し、サーバーURLHandlerはこれらを受信し、クライアントに応答するためのコールバックをキューに追加します。

  • サーバーが新しいメッセージを受け取ると、onmessageイベントが発生し、コールバックをループ処理してメッセージを送信します。

  • クライアント側のJSはメッセージを受信し、それをページに追加してから、この新しいメッセージID以降の更新を要求します。

31
mikemaccana

クライアントは通常の非同期のAJAXリクエストのように見えると思いますが、戻ってくるまでには長い時間がかかると思われます。

サーバーはこのようになります。

while (!hasNewData())
    usleep(50);

outputNewData();

そのため、AJAX要求はサーバーに送信され、おそらく最後の更新時のタイムスタンプが含まれているので、hasNewData()はすでに取得したデータを認識できます。その後、サーバーは新しいデータが利用可能になるまでスリープ状態でループします。その間ずっと、AJAXリクエストはまだ接続されていて、データを待っているだけです。最後に、新しいデータが利用可能になると、サーバーはそれをAJAXリクエストに渡して接続を閉じます。

24
Greg

ここで は、C#でロングポーリングに使用するクラスです。基本的に6つのクラスがあります(下記参照)。

  1. Controller :有効なレスポンスを作成するために必要なアクションを処理します(db操作など)。
  2. プロセッサ :Webページ(それ自体)との非同期通信を管理します。
  3. IAsynchProcessor :サービスはこのインタフェースを実装するインスタンスを処理します
  4. サービス :IAsynchProcessorを実装するリクエストオブジェクトを処理する
  5. リクエスト :あなたのレスポンスを含むIAsynchProcessorラッパー(オブジェクト)
  6. レスポンス :カスタムオブジェクトまたはカスタムフィールドが含まれています
17
Prisoner ZERO

これはPHP&jQueryを使用してロングポーリングを行う方法についての5分間の素晴らしいスクリーンキャストです: http://screenr.com/SNH

コードは上記の dbr の例とよく似ています。

16
Sean O

Erik DubbelboerによるPHPの簡単なロングポーリングの例Content-type: multipart/x-mixed-replaceヘッダーの使用

<?

header('Content-type: multipart/x-mixed-replace; boundary=endofsection');

// Keep in mind that the empty line is important to separate the headers
// from the content.
echo 'Content-type: text/plain

After 5 seconds this will go away and a cat will appear...
--endofsection
';
flush(); // Don't forget to flush the content to the browser.


sleep(5);


echo 'Content-type: image/jpg

';

$stream = fopen('cat.jpg', 'rb');
fpassthru($stream);
fclose($stream);

echo '
--endofsection
';

そして、これがデモです。

http://dubbelboer.com/multipart.php

12
Jasdeep Khalsa

私は this を使ってCometを理解しました。JavaGlassfishサーバーを使ってCometをセットアップし、cometdaily.comを購読することで他の多くの例を見つけました。

11
adam

このブログ記事 を見てください。これには、Python/Django / gevent の簡単なチャットアプリのコードがあります。

9
Denis Bilenko

以下はInform8 Web用に開発したロングポーリングソリューションです。基本的にはクラスをオーバーライドしてloadDataメソッドを実装します。 loadDataが値を返すか、操作がタイムアウトすると、結果を出力して戻ります。

スクリプトの処理に30秒以上かかる場合は、set_time_limit()呼び出しをもっと長いものに変更する必要があるかもしれません。

Apache 2.0ライセンスgithubの最新バージョン https://github.com/ryanhend/Inform8/blob/master/Inform8-web/src/config/lib/Inform8/longpoll/LongPoller.php

ライアン

abstract class LongPoller {

  protected $sleepTime = 5;
  protected $timeoutTime = 30;

  function __construct() {
  }


  function setTimeout($timeout) {
    $this->timeoutTime = $timeout;
  }

  function setSleep($sleep) {
    $this->sleepTime = $sleepTime;
  }


  public function run() {
    $data = NULL;
    $timeout = 0;

    set_time_limit($this->timeoutTime + $this->sleepTime + 15);

    //Query database for data
    while($data == NULL && $timeout < $this->timeoutTime) {
      $data = $this->loadData();
      if($data == NULL){

        //No new orders, flush to notify php still alive
        flush();

        //Wait for new Messages
        sleep($this->sleepTime);
        $timeout += $this->sleepTime;
      }else{
        echo $data;
        flush();
      }
    }

  }


  protected abstract function loadData();

}
9
Ryan Henderson

dbr というコードをありがとう。 long_poller.htm の行の周りのちょっとしたタイプミス

1000 /* ..after 1 seconds */

私はそれがあるべきだと思います

"1000"); /* ..after 1 seconds */

それが機能するために。

興味のある人のために、私はDjangoと同等のものを試しました。新しいDjangoプロジェクトを開始します。 lp のようにします。

Django-admin.py startproject lp

App/ msgsrv をメッセージサーバー用に呼び出します。

python manage.py startapp msgsrv

次の行を settings.py に追加して templates ディレクトリを作成します。

import os.path
PROJECT_DIR = os.path.dirname(__file__)
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, 'templates'),
)

次のように urls.py にURLパターンを定義します。

from Django.views.generic.simple import direct_to_template
from lp.msgsrv.views import retmsg

urlpatterns = patterns('',
    (r'^msgsrv\.php$', retmsg),
    (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}),
)

そしてmsgsrv / views.py は次のようになります。

from random import randint
from time import sleep
from Django.http import HttpResponse, HttpResponseNotFound

def retmsg(request):
    if randint(1,3) == 1:
        return HttpResponseNotFound('<h1>Page not found</h1>')
    else:
        sleep(randint(2,10))
        return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10)))

最後に、templates / long_poller.htm はタイプミスを訂正した上で同じであるべきです。お役に立てれば。

8
xoblau

これはPHPが非常に悪い選択であるシナリオの1つです。前述したように、あなたはあなたのApacheワーカー全員を非常に迅速にこのようなことをして結ぶことができます。 PHPは開始、実行、停止用に構築されています。開始、待機、実行、停止用に構築されたものではありません。あなたは非常に早くあなたのサーバーを使い果たし、あなたが信じられないほどのスケーリング問題を抱えていることに気付くでしょう。

そうは言っても、あなたはまだPHPを使ってこれを行うことができ、nginx HttpPushStreamModuleを使ってあなたのサーバを殺さないようにすることができます: http://wiki.nginx.org/HttpPushStreamModule

あなたはnginxをApacheの前に(あるいは他のものは何でも)設定します、そしてそれは同時接続を開いたままにしておくことの世話をするでしょう。バックグラウンドジョブで実行できる内部アドレスにデータを送信することでペイロードで応答するか、または新しい要求が到着するたびに待機している人々にメッセージを送信するだけです。これにより、PHPプロセスが防止されます。長時間の投票中に開いて座っている。

これはPHPに限定されず、nginxを使用して任意のバックエンド言語で実行できます。同時オープン接続の負荷はNode.jsに等しいので、最大の利点は、このような何かのためにあなたがNEEDINGノードから抜け出すことです。

あなたは他の多くの人々がロングポーリングを達成するために他の言語ライブラリに言及しているのを見ます、そしてそれは正当な理由です。 PHPは、このような振る舞いに当然適していません。

7
brightball

ロングポーリングの代わりにWebソケットを考慮しないのはなぜでしょうか。それらは非常に効率的で設定が簡単です。しかし、それらは最新のブラウザでのみサポートされています。これは クイックリファレンス です。

4
shasi kanth

WS-Iグループは "Reliable Secure Profile" という名前のGlass Fishと .NETの実装 それは明らかに inter-operating well)を持つものを公開しました。

運がよければ Javascript の実装もあります).

HTTP Duplex。 を使用するSilverlightの実装もあります。 Pushが発生したときにjavascriptをSilverlight オブジェクトに接続してコールバックを取得できます。

市販の有料版 もあります)。

3
random65537

ASP.NET MVCの実装については、SignalRをご覧ください NuGetで利用可能 .. NuGetは頻繁に Gitソース から古くなっていることに注意してください。 。

SignalRの詳細については Scott Hanselmanによるブログ

2

あなたはicomet( https://github.com/ideawu/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をサポートしています。

2
ideawu

わかりました、私は彼らがどう思うかわかりません、しかし、私はあなたに純粋でネイティブのそして輝いた意見だけを提供することができます。

1)テキストファイルを呼び出すためにユーザーごとに5秒ごとにあなたのajaxをしなさい、その値は時々0で時々1です2)誰かがあなたのクライアントメッセージを送るならphpによってid + username.txtの名前のファイルを作成します最初のステップの中で呼び出されるファイル)3)メッセージがデータベースで送信されているとき、テキストファイルに値1を挿入します。4)テキストファイルの値が1の場合、クライアント側はサーバー側でgetメッセージを取得します。 5)結局のところ、自分のテキストファイルの中に1を挿入するクライアント呼び出し関数です。

0
Ilia Weber

最も単純なNodeJS

const http = require('http');

const server = http.createServer((req, res) => {
  SomeVeryLongAction(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(8000);

// the long running task - simplified to setTimeout here
// but can be async, wait from websocket service - whatever really
function SomeVeryLongAction(response) {
  setTimeout(response.end, 10000);
}

Express for exmapleの実稼働に関するシナリオでは、ミドルウェアでresponseを取得します。必要なことを行い、長いポーリングされたすべてのメソッドをMapまたは何か(他のフローに表示される)にスコープし、準備ができたらいつでも<Response> response.end()を呼び出すことができます。長いポーリング接続について特別なことはありません。残りは、アプリケーションを通常どのように構成するかです。

スコーピングすることの意味がわからない場合、これはあなたにアイデアを与えるはずです

const http = require('http');
var responsesArray = [];

const server = http.createServer((req, res) => {
  // not dealing with connection
  // put it on stack (array in this case)
  responsesArray.Push(res);
  // end this is where normal api flow ends
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

// and eventually when we are ready to resolve
// that if is there just to ensure you actually 
// called endpoint before the timeout kicks in
function SomeVeryLongAction() {
  if ( responsesArray.length ) {
    let localResponse = responsesArray.shift();
    localResponse.end();
  }
}

// simulate some action out of endpoint flow
setTimeout(SomeVeryLongAction, 10000);
server.listen(8000);

ご覧のとおり、すべての接続に本当に応答することができます。すべてのリクエストにはidがあるため、mapを使用して特定のAPI以外の呼び出しにアクセスできるはずです。

0
sp3c1