はじめに (対象読者・この記事でわかること)
本記事は、Web アプリケーションのリアルタイム性を高めるために PHP で Comet(長時間接続)を実装しようとしているエンジニアを対象としています。
特に「サーバーからデータを送信したはずなのに、クライアント側に何も届かない」現象に心当たりがある方に向け、以下のことが理解できます。
- PHP で Comet を実装する際に陥りやすい典型的な落とし穴
- 出力がブロックされる原因(バッファリング、タイムアウト、ヘッダー設定など)
- 実際のコード例を交えたデバッグ手順と、問題解決の具体的な対策
この記事を書いたきっかけは、社内プロジェクトで数回にわたり同様の不具合が発生し、原因究明に多くの時間を費やした経験です。共通の課題を解決する手がかりになれば幸いです。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- 基本的な PHP 文法と
session_start()、ob_flush()系関数の使い方 - HTTP のヘッダー、ステータスコード、長時間接続(Comet/長輪読)に関する概念
- 開発環境に Apache か Nginx がインストールされており、設定ファイル(
httpd.conf、nginx.conf)を編集できること
Comet の概要と PHP で実装する背景
Comet とは、サーバー側がクライアントからのリクエストを保持し続け、イベントが発生したときに即座にデータをプッシュする技術です。
WebSocket が登場する前は、長時間接続やサーバー送信イベント(Server‑Sent Events, SSE)として実装され、チャットやリアルタイム通知、株価表示などで利用されました。
PHP は本来リクエスト/レスポンス型のスクリプト言語であり、標準設定ではリクエストが完了するとすぐに出力バッファがフラッシュされます。そのため、Comet のように 「途中でデータを少しずつ送信」 するには、バッファリング制御やサーバー側のタイムアウト設定を意識的に調整する必要があります。
主な課題は次の通りです。
- 出力バッファリング
output_bufferingが有効だと、echoした内容が一定サイズになるまでクライアントに送られません。 - PHP の max_execution_time
デフォルト 30 秒でスクリプトが強制終了し、途中のデータが失われます。 - Web サーバーのタイムアウト
Apache のTimeout、Nginx のproxy_read_timeoutなどが短いと、接続が切断されます。 - ヘッダーの設定ミス
Content-Type: text/event-streamを忘れると、ブラウザが SSE として扱わず、データが蓄積されます。
これらを正しく設定しないと、サーバーはデータを出力し続けていてもクライアント側は「何も受信していない」状態になることが多いです。
PHP で Comet を実装しデータが出力されないときの対処法
以下では、実際に動作するサンプルコードとともに、典型的な問題点とその解決策をステップごとに解説します。
ステップ1:基本的な SSE(Server‑Sent Events)サーバーの作成
Php<?php // file: comet.php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); // PHP の実行時間制限を解除 set_time_limit(0); // 出力バッファを無効化 while (ob_get_level() > 0) { ob_end_flush(); } ob_implicit_flush(true); // 無限ループでメッセージを送信 $counter = 0; while (true) { // 1 秒ごとにデータを送信 echo "event: ping\n"; echo "data: " . json_encode(['time' => date('c'), 'count' => $counter]) . "\n\n"; $counter++; // バッファが送信されるようにフラッシュ flush(); // 1 秒待機 sleep(1); } ?>
このコードは最小限の SSE 送信スクリプトです。重要なのは ヘッダー設定 と flush() の組み合わせです。ob_implicit_flush(true) を呼び出すと、echo のたびに自動的にフラッシュが走ります。
期待される動作
ブラウザのコンソールで以下のように確認できます。
Jsconst evtSource = new EventSource('comet.php'); evtSource.onmessage = e => console.log('受信:', e.data);
1 秒ごとに JSON 文字列がコンソールに表示されるはずです。
ステップ2:サーバー設定の見直し
2‑1. PHP.ini の調整
output_buffering = Off
implicit_flush = On
max_execution_time = 0
output_buffering をオフにすると、PHP が内部でデータを溜め込むのを防げます。max_execution_time = 0(無制限)に設定しないと、スクリプトが途中で終了してしまいます。
2‑2. Apache の設定例
Apache# httpd.conf もしくは .htaccess Header set Cache-Control "no-cache" Header set Connection "keep-alive" Timeout 300 ProxyTimeout 300
Timeout のデフォルトは 300 秒ですが、長時間の接続が必要ならさらに延長するか、KeepAliveTimeout を調整します。
2‑3. Nginx の設定例
Nginxlocation /comet.php { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_buffering off; # バッファリング無効化 proxy_read_timeout 3600; # 必要に応じて延長 }
fastcgi_buffering off; がキーです。これが無いと、Nginx がバックエンドからの出力を内部でバッファし、クライアントに遅延させます。
ハマった点やエラー解決
| 現象 | 原因 | 解決策 |
|---|---|---|
| クライアントに何も届かない | output_buffering が有効 |
php.ini で output_buffering = Off、もしくは ob_end_flush() を使用 |
| 30 秒で接続が切れる | max_execution_time がデフォルト 30 秒 |
set_time_limit(0) または php.ini の max_execution_time = 0 |
| データが 5 秒ごとにまとめて送られる | サーバー側のバッファリング(Apache の mod_deflate や Nginx の fastcgi_buffering) |
mod_deflate の SetEnv no-gzip 1、Nginx の fastcgi_buffering off |
| ブラウザがイベントを受信しない | Content-Type が間違っている |
header('Content-Type: text/event-stream'); を必ず設定 |
| ネットワーク遅延で途中で切れる | proxy_read_timeout が短い |
proxy_read_timeout を 300 秒以上に設定 |
解決策のまとめ
- ヘッダーを正しく設定し、
text/event-streamで送信。 - 出力バッファを無効化し、
flush()とob_implicit_flush(true)で即時送信。 - PHP の実行時間制限とバッファリング設定を
php.iniで調整。 - Web サーバー側のタイムアウトとバッファリング(Apache の
Timeout、Nginx のfastcgi_buffering)を長く、もしくはオフに。 - デバッグ時はログとブラウザのコンソールを併用し、サーバー側の出力が実際に送られているかを確認。
これらを順に行うことで、PHP で実装した Comet が正しくデータをストリームできるようになります。
まとめ
本記事では、PHP による Comet(長時間接続)実装時に「データがクライアントに届かない」現象の原因と、実践的な解決策を解説しました。
- ヘッダーとバッファリング を正しく設定すれば、サーバーから即時にデータをプッシュできる。
- PHP と Web サーバーのタイムアウト を十分に伸ばすことで、長時間の接続が維持できる。
- 実装例とデバッグ手順 を踏めば、同様の問題に遭遇した際に迅速に対処できる。
この記事を通じて、読者は PHP で安定したリアルタイム配信基盤を構築できる自信を得られるはずです。次回は、同様のコンセプトを WebSocket と比較し、適材適所の選択肢を紹介する予定です。
参考資料
- PHP: Output Buffering - Manual
- MDN: Server‑Sent Events
- Apache HTTP Server Documentation – Timeout Directive
- NGINX: FastCGI Parameters
