PHPでEvent、PCNTL、POSIX、Streamsを使用したマルチプロセスフレームワークの実現

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);

タグ: PHP event pcntl posix streams

5月15日 17:45 投稿