回答:
最初に思ったよりも簡単です。基本的に、送信したいデータが利用可能になるまで(たとえば、新しいメッセージが到着するまで)、ページは何も実行しません。
これは本当に基本的な例で、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サーバーでこれを実行すると、すべての「ワーカースレッド」がすぐに拘束され、他の要求に応答できなくなります。これを回避する方法はいくつかありますが、リクエストごとに1つのスレッドに依存しない、Pythonのtwistedなどの「ロングポーリングサーバー」。cometDは人気のあるもの(複数の言語で利用可能)であり、トルネードはそのようなタスク(FriendFeedのロングポーリングコード用に作成された)のために特別に作られた新しいフレームワークです...しかし、簡単な例として、Apacheは十分すぎるほどです!このスクリプトは任意の言語で簡単に作成できます(非常に一般的なため、Apache / PHPを選択しましたが、たまたまローカルで実行しています)。
次に、JavaScriptで上記のファイル(msg_srv.php
)を要求し、応答を待ちます。取得すると、データに基づいて行動します。次に、ファイルを要求して再度待機し、データを操作します(そして繰り返します)。
このようなページの例を次に示します。ページが読み込まれると、msgsrv.php
ファイルの初期リクエストが送信されます。成功した場合は、メッセージを#messages
divに追加し、1秒後にもう一度waitForMsg関数を呼び出します。これは待機をトリガーします。
1秒setTimeout()
は本当に基本的なレートリミッターです。これがなくても正常に機能しますが、msgsrv.php
常に(たとえば、構文エラーで)瞬時に戻る場合は、ブラウザーをフラッディングすると、すぐにフリーズする可能性があります。これには、ファイルに有効なJSON応答が含まれているかどうかを確認したり、1分あたり1秒あたりのリクエスト数の合計を維持したり、適切に一時停止したりすることをお勧めします。
ページにエラーが発生した場合、エラーを#messages
divに追加し、15秒待機してから再試行します(各メッセージの後に1秒待機する方法と同じ)
このアプローチの良い点は、非常に弾力性があることです。クライアントのインターネット接続が停止すると、タイムアウトしてから再接続します。これは、ポーリングの動作時間に固有であり、複雑なエラー処理は必要ありません。
とにかく、long_poller.htm
jQueryフレームワークを使用したコード:
<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>
sleep(rand(2,10));
何を行うかです。何もしないために、100ミリ秒ごとにデータベースをポーリングしますか?いつ死ぬのか?
私はsloshの一部として本当に簡単なチャットの例を持っています。
編集:(誰もがここにコードを貼り付けているため)
これは、ロングポーリングとsloshを使用した完全なJSONベースのマルチユーザーチャットです。これは呼び出し方法のデモなので、XSSの問題は無視してください。これを最初にサニタイズせずにデプロイすることはできません。
クライアントは常にサーバーに接続しているため、だれかがメッセージを送信するとすぐに、だれでもほぼ瞬時にそれを確認できるはずです。
<?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 <dustin+html@spy.net> -->
<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>
Tornadoはロングポーリング用に設計されており、/ examples / chatdemoに最小限の(数百行のPythonの)チャットアプリが含まれています(サーバーコードとJSクライアントコードを含む)。それはこのように動作します:
クライアントはJSを使用して(最後のメッセージの数)以降の更新を要求し、サーバーURLHandlerはこれらを受信して、クライアントに応答するためのコールバックをキューに追加します。
サーバーが新しいメッセージを取得すると、onmessageイベントが発生し、コールバックをループしてメッセージを送信します。
クライアント側のJSはメッセージを受信してページに追加し、この新しいメッセージID以降の更新を要求します。
クライアントは通常の非同期AJAXリクエストのように見えますが、戻ってくるまでに「長い時間」かかると思います。
サーバーは次のようになります。
while (!hasNewData())
usleep(50);
outputNewData();
したがって、AJAX要求はサーバーに送信され、おそらく最後に更新されたときのタイムスタンプが含まれてhasNewData()
いるため、すでに取得しているデータがわかります。サーバーは、新しいデータが利用可能になるまでループスリープ状態になります。その間、AJAXリクエストは接続されたままで、データを待っているだけです。最後に、新しいデータが利用可能になると、サーバーはそれをAJAXリクエストに渡し、接続を閉じます。
C#でのロングポーリングに使用するいくつかのクラスを次に示します。基本的に6つのクラスがあります(以下を参照)。
これは、PHPとjQueryを使用してロングポーリングを行う方法に関する5分間の素晴らしいスクリーンキャストです。http: //screenr.com/SNH
コードは、上記のdbrの例と非常に似ています。
以下は、ヘッダーを使用した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
';
そしてここにデモがあります:
以下は、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();
}
コードをありがとう、dbr。行の周りのlong_poller.htmのほんの小さなタイプミス
1000 /* ..after 1 seconds */
そうだと思う
"1000"); /* ..after 1 seconds */
それが機能するために。
興味がある人のために、私はDjangoの同等物を試しました。新しいDjangoプロジェクトを開始します。長いポーリングにはlpと言います。
django-admin.py startproject lp
メッセージサーバーのアプリmsgsrvを呼び出します。
python manage.py startapp msgsrv
次の行をsettings.pyに追加して、テンプレートディレクトリを作成します。
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は上記と同じで、タイプミスが修正されています。お役に立てれば。
"15000"
は、構文エラーです。setTimeoutは、2番目のパラメーターとして整数を取ります。
これは、PHPが非常に悪い選択であるシナリオの1つです。前述のように、Apacheワーカーのすべてを非常に迅速に拘束して、このようなことを行うことができます。PHPは、起動、実行、停止用に構築されています。起動、待機、実行、停止用に構築されていません。サーバーをすぐに停止して、信じられないほどのスケーリングの問題があることに気づくでしょう。
そうは言っても、PHPでこれを行うことができ、nginx HttpPushStreamModule:http ://wiki.nginx.org/HttpPushStreamModuleを使用してサーバーを強制終了しないでください。
nginxをApache(またはその他)の前にセットアップすると、同時接続を開いたままにしておくことができます。バックグラウンドジョブで実行できる内部アドレスにデータを送信してペイロードで応答するか、新しいリクエストが着信するたびに待機していたユーザーにメッセージを送信します。これにより、PHPのプロセスが長時間のポーリング中に開いたままにならないようにします。
これはPHPに限定されるものではなく、任意のバックエンド言語でnginxを使用して実行できます。同時オープン接続の負荷はNode.jsと等しいため、最大の特典は、このようなもののためにノードを必要としないことです。
他の多くの人々がロングポーリングを達成するために他の言語ライブラリに言及しているのを見ていますが、それはもっともな理由があります。PHPは自然にこのタイプの振る舞いのためにうまく構築されていません。
ロングポーリングの代わりにWebソケットを検討してみませんか?彼らは非常に効率的で簡単にセットアップできます。ただし、最新のブラウザでのみサポートされています。こちらがクイックリファレンスです。
WS-Iグループは、Glass Fishと.NETを実装した「Reliable Secure Profile」と呼ばれるものを公開しました。
運が良ければJavascriptの実装もあります。
HTTPデュプレックスを使用するSilverlight実装もあります。 あなたはできるのSilverlightにjavascriptの接続のプッシュが発生したときにコールバックを取得するオブジェクト。
商用の有料版もあります。
ASP.NET MVC実装については、NuGetで利用可能な SignalR を見てください。NuGetは、非常に頻繁にコミットされるGitソースからは古くなっていることが多いことに注意してください。
Scott Hanselmanのブログで SignalRの詳細を読んでください。
libeventで構築されたC1000K C ++コメットサーバーであるicomet(https://github.com/ideawu/icomet)を試すことができます。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をサポートしています。
最も単純な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);
}
たとえばresponse
ミドルウェアを使用する場合のExpressの運用に関するシナリオ。あなたがする必要があることを行い、長いポーリングされたすべてのメソッドを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
リクエストごとに存在するので、マップを使用して、APIの特定のout呼び出しにアクセスできるはずです。