- Reactorパターンについてはこちらの記事等を参照。
https://github.com/phpsphb/book-examples/blob/master/reactor/reactor.php
<?php
namespace SocketProgrammingHandbook;
final class StreamSelectLoop {
private $readStreams = [];
private $readHandlers = [];
private $writeStreams = [];
private $writeHandlers = [];
private $running = true;
function addReadStream($stream, callable $handler) {
if (empty($this->readStreams[(int) $stream])) {
$this->readStreams[(int) $stream] = $stream;
$this->readHandlers[(int) $stream] = $handler;
}
}
function addWriteStream($stream, callable $handler) {
if (empty($this->writeStreams[(int) $stream])) {
$this->writeStreams[(int) $stream] = $stream;
$this->writeHandlers[(int) $stream] = $handler;
}
}
function removeReadStream($stream) {
unset($this->readStreams[(int) $stream]);
}
function removeWriteStream($stream) {
unset($this->writeStreams[(int) $stream]);
}
function removeStream($stream) {
$this->removeReadStream($stream);
$this->removeWriteStream($stream);
}
/**
* Runs the event loop, which blocks the current process. Make sure you do
* any necessary setup before running this.
*/
function run() {
while ($this->running) {
$read = $this->readStreams;
$write = $this->writeStreams;
$except = null;
if ($read || $write) {
@stream_select($read, $write, $except, 0, 100);
foreach ($read as $stream) {
$this->readHandlers[(int) $stream]($stream);
}
foreach ($write as $stream) {
$this->writeHandlers[(int) $stream]($stream);
}
} else {
usleep(100);
}
}
}
}
Reactorパターンを使うべき場合:
- 複数のクライアントコネクションを同時に処理する必要があるが、サーバで複雑なコネクションマネジメントをしたくない場合
- メモリが限られていて、マシン上で1つのサーバプロセスしか実行できない場合
fork
が遅かったりメモリ消費量が多いプラットフォーム(主にWindows)で実行する必要がある場合- クライアントを処理している最中にコードがクラッシュしても、新しいサーバプロセスを再起動すれば住む場合
Reactorパターンを使うべきでない場合:
fork
が高速でメモリ効率が良いプラットフォーム(主にUNIX、Linux)で実行する場合- 本当に並列処理を行う必要がある場合(Reactorは完全な並列ではない)
- メモリが潤沢にある場合
PHPライブラリ
PHPによるReactorパターンの実装として著名なものにReactPHPライブラリがある。
また、stream_select
はselect(2)
に依存しているが、より高パフォーマンスのシステムコールにepoll(2)
やkqueue(2)
などがある。
PHPでepoll(2)
等を使いたい場合、これらのシステムコールを抽象化したライブラリであるlibeventのPHP向けインタフェースLibevent等を使用することができる。