PHPを使用して、Event、PCNTL、POSIX、およびStreamsライブラリを組み合わせて、効率的なマルチプロセスベースのサーバーフレームワークを構築する方法について説明します。このアプローチは、C++のような低レベル言語を使用せずに、PHPだけで高性能なネットワークリスニングを実現することを目指しています。
1. デーモンプロセスの作成
PHPでデーモンプロセスを作成する際には、いくつかの基本ステップがあります。以下のコード例では、子プロセスが親プロセスから独立し、バックグラウンドで実行されるように設定されています。<?php
function createDaemon() {
$pid = pcntl_fork();
if ($pid == -1) {
die('プロセスのフォークに失敗しました');
} elseif ($pid > 0) {
exit(0); // 親プロセスを終了
}
// 子プロセス内での処理
if (posix_setsid() === -1) {
die('新しいセッションIDの取得に失敗しました');
}
chdir('/'); // ルートディレクトリに移動
umask(0); // ファイル作成マスクをクリア
// 不要なファイルディスクリプタを閉じる
foreach (range(0, 1024) as $fd) {
@fclose($fd);
}
}
createDaemon();
echo "デーモンプロセスが開始されました\n";
2. シグナルの扱い
UNIXシステムでは、シグナルを使用してプロセス間通信を行います。以下は、主要なシグナルの一覧です。| 番号 | 名前 | 説明 |
|---|---|---|
| 1 | SIGHUP | 接続が切断された場合に送られる。 |
| 2 | SIGINT | 中断要求(Ctrl+C)。 |
| 9 | SIGKILL | 強制終了要求。 |
| 15 | SIGTERM | 通常の終了要求。 |
3. PHP StreamsとSocketsの違い
PHPでは、SocketsとStreamsの両方が利用可能です。ただし、Socketsはより詳細な制御が必要ですが、StreamsはPHPコア機能としてサポートされており、操作が簡単です。4. 実装例: 単一プロセスのStreamベースサーバー
単純なTCPサーバーの例を示します。<?php
$server = stream_socket_server('tcp://127.0.0.1:9800', $errno, $errstr);
if (!$server) {
die("サーバー起動失敗: $errstr ($errno)\n");
}
stream_set_blocking($server, false);
while (true) {
if (($client = @stream_socket_accept($server, 0)) !== false) {
fwrite($client, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello\n");
fclose($client);
}
usleep(10000);
}
5. 多重プロセスの実装
最後に、複数のワーカープロセスを持つサーバーの例を示します。<?php
class MultiProcessServer {
private $workers = [];
private $socket;
public function __construct($address) {
$this->socket = stream_socket_server($address, $errno, $errstr);
if (!$this->socket) {
die("ソケット作成エラー: $errstr ($errno)\n");
}
}
public function start($numWorkers) {
for ($i = 0; $i < $numWorkers; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
die('プロセスフォーク失敗');
} elseif ($pid > 0) {
$this->workers[] = $pid;
} else {
$this->handleRequests();
exit(0);
}
}
$this->monitorWorkers();
}
private function handleRequests() {
while (true) {
$client = @stream_socket_accept($this->socket, 0);
if ($client) {
fwrite($client, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello\n");
fclose($client);
}
usleep(10000);
}
}
private function monitorWorkers() {
while (true) {
$status = 0;
$pid = pcntl_wait($status, WNOHANG);
if ($pid > 0) {
echo "プロセス $pid が終了しました\n";
unset($this->workers[array_search($pid, $this->workers)]);
}
usleep(100000);
}
}
}
$server = new MultiProcessServer('tcp://0.0.0.0:9800');
$server->start(4);