2008年7月9日水曜日

cometなんて知らなかったサル

なんか閃いた(サル的に)のでブラウザでチャットする方法を考えてみた。XMLHttpRequest(いわゆるAjaxのこと、以下XHR)を使えば容易に実現できそうに思える。

まず思いつくのがクライアントから定期的(1秒毎とか)にリクエストさせる方法(ポーリングというらしい)。この方法だと例えば5人が10分チャットすると5(人)×600(秒)=3000リクエスト。そしてユーザの発言が反映されるまで最大で1秒の遅延が発生する。リクエスト間隔を短くすれば遅延を短く出来るがリクエストが増える。リクエストを減らすと遅延が増える。

そこで閃いた(サル的に)のがブラウザからのリクエストに対してサーバサイドでセッションを保持し、ブラウザではXHR.readyState=3で受信する方法。リクエスト数=ユーザ数となり、リクエスト数という観点からは最良の方法と思われたが、実際にやってみたらFirefoxでしか動かない。IEもSafariもreadyState=3に対応していなかった。ソースは下記。サーバ側の処理(1-2)がミソ。

1-1
function broadcast(elem){
XHR.open('POST', 'broadcast.php',true);
XHR.onreadystatechange = function() {
if (XHR.readyState == 3 && XHR.status == 200) {
elem.innerHTML = decodeURIComponent(XHR.responseText);
}
}
XHR.send(null);
}

1-2
<?php
//broadcast.php
$stat_a = stat('chat.txt');
while($stat = stat('chat.txt')){
if($stat[9] != $stat_a[9]){
$str = file_get_contents('chat.txt');
echo "<div>".date('H:i:s')." ".urlencode(strip_tags($str))."</div>";
ob_flush();
flush();
$stat_a[9] = $stat[9];
}
usleep(0.1 * 1000 * 1000);
set_time_limit(10);
clearstatcache();
}
?>

IEでも動くいい方法がないか考えてみた。発言があるまでセッションを保持。発言があったらレスポンスしいったんセッションは終了。そしてすぐにまたXHRリクエスト。というのを繰り返す。これだと5人でチャットして全体で100の発言があったら5(人)×100(発言)=500リクエスト。ソースは下記。再帰的呼び出しでかっこいい出来(サル的に)。

2-1
function broadcast(elem){
XHR.open('POST', 'broadcast.php',true);
XHR.onreadystatechange = function() {
if (XHR.readyState == 4 && XHR.status == 200) {
elem.innerHTML = elem.innerHTML + decodeURIComponent(XHR.responseText);
broadcast(elem); //←再帰的呼び出し
}
}
XHR.send(null);
}

2-2
<?php
//broadcast.php
$stat_a = stat('chat.txt');
while($stat = stat('chat.txt')){
if($stat[9] != $stat_a[9]){
$str = file_get_contents('chat.txt');
echo "<div>".date('H:i:s')." ".urlencode(strip_tags($str))."</div>";
$stat_a[9] = $stat[9];
exit;
}
usleep(0.1 * 1000 * 1000);
set_time_limit(10);
clearstatcache();
}
?>

そして調べてみたらAjaxセッションを保持するという実装スタイルはcometという名前が付いていて別に新しくなかった。閃いたつもりだったのに(サルが)。XHR.readyState=3を使ったcometは今のところFirefoxでしか出来ないが、とても可能性を感じた。

以下てきとーにソースの説明

XHR.openでPOSTメソッドを使っているのはブラウザのキャッシュを回避するため。IEではGETメソッドのAjaxは強力にキャッシュされる。XHRは生成済みのXMLHttpRequestオブジェクトと思ってちょうだい。

1-2のob_flush()とflush()
PHPではスクリプトの実行が終わってから出力結果をブラウザに送信するが、ob_flush()とflush()を使うことでスクリプトの途中までの出力結果をブラウザに送信できる。

1-2のclearstatcache()
stat関数で取得したファイル情報はキャッシュされるので一度取得したファイル情報が更新された可能性がある場合はclearstatcache()でキャッシュをクリアしてからファイル情報を取得する必要がある。

1-2、2-2のset_time_limit(10)
スクリプトの実行でphp.iniで設定した秒数を超えるとFatal errorになるので、これを回避するためにset_time_limit(10)で期限を更新。set_time_limit()を呼び出すたびに秒数はリセットされる。数字は秒数。短すぎなければ何でも良い。

0 件のコメント: