stream_select
(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)
stream_select — select() システムコールと同等の操作を、 ストリームの配列に対して seconds と microseconds で指定されたタイムアウト時間をもって行う
説明
?array
&$read
,?array
&$write
,?array
&$except
,?int
$seconds
,?int
$microseconds
= null
): int|false
stream_select() はストリームの配列を受け取ると、 それらの状態が変化するまで待ちます。機能としては、ストリームに対して 働くという点以外では socket_select() と同一です。
パラメータ
read
-
read
配列に列挙されたストリームに対しては、 何らかのデータがそのストリーム内で読み出せる状態にあるかどうか 監視が行われます (より正確にいえば、ブロックしないで読み出せる状態かどうか - 特にストリームが EOF に達したかどうか、です。このとき、 fread() は長さ 0 の文字列を返します)。 write
-
write
配列に列挙されたストリームに対しては、 ブロックしないで書き込みができるかどうかの監視が行われます。 except
-
except
配列に列挙されたストリームに対しては、 重大な例外("帯域外の")データが発生したかどうかの監視が行われます。注意:
stream_select() の終了時には、 どのストリームの状態が実際に変化したのかが分かるよう、 配列
read
、write
およびexcept
に変更が加えられます。 但し、配列のキーは保存されます。 seconds
-
seconds
とmicroseconds
は、一体となって、timeout パラメータを表現します。seconds
は秒数を指定し、一方microseconds
はマイクロ秒数を指定します。 timeout は、stream_select() の実行から戻るまでの時間の上限です。seconds
およびmicroseconds
の両方に0
を指定すると stream_select() はデータを待たずに一瞬で戻ります。 これは現在のストリームの状態を示します。seconds
がnull
(タイムアウトなし) の場合、 stream_select() はブロックしつづけ、調べている ストリームのひとつでイベントが発生する(あるいはシグナルがシステム コールを中断する)まで終了しません。警告タイムアウト値に
0
を指定すると、ストリームの 状態を即時に取得することが可能です。しかし、ループ内でタイムアウト0
を指定するのは良い考えではありません。 そうすると大量の CPU 時間を消費してしまいます。タイムアウト値を数秒にするとかなりましになります。しかし、どうしても 他のコードを同時に実行させながらチェックをする必要がある場合には、 少なくとも
200000
マイクロ秒以上のタイムアウトを 設定するようにしましょう。これであなたのスクリプトの CPU 使用量を 抑えることができます。タイムアウト値は、あくまでも経過時間の最大値であることを覚えておきましょう。 stream_select() は、指定したストリームが使用可能に なるとすぐに結果を返します。
microseconds
-
seconds
の説明を参照ください。
戻り値
成功した場合 stream_select() は、変更された
配列に何個のストリームリソースが格納されたかを示す数を返します。
もしタイムアウトの時間内に何も規定された事象が起こらなかった場合は
0 になることもあります。エラーの際は false
を返し、警告を発生させます
(システムコールが別のシグナルによって中断された場合などに起こりえます)。
変更履歴
バージョン | 説明 |
---|---|
8.1.0 |
microseconds は、nullable になりました。
|
例
例1 stream_select() の例
この例では、$stream1
あるいは
$stream2
のどちらかに読み込めるデータが
到達したかどうかを調べます。
タイムアウトが 0
なので、すぐに結果を返します。
<?php
/* read 配列を用意 */
$read = array($stream1, $stream2);
$write = NULL;
$except = NULL;
if (false === ($num_changed_streams = stream_select($read, $write, $except, 0))) {
/* エラー処理 */
} elseif ($num_changed_streams > 0) {
/* 少なくとも 1 つのストリームに何らかの事象が起こりました。*/
}
?>
注意
注意:
現在の Zend Engine の実装上の制約により、
null
のような定数を この関数の参照渡しが行われるパラメータに直接指定することはできません。 代わりに一時的な変数を指定するか、一番左の変数が一時的な変数になるような 式を指定してください:<?php
$e = NULL;
stream_select($r, $w, $e, 0);
?>
注意:
エラーかどうかをチェックするには
===
を使ってください。 stream_select() は 0 を返すことがあるため、その場合false
と==
演算子で比較するとtrue
と評価されてしまうからです:<?php
$e = NULL;
if (false === stream_select($r, $w, $e, 0)) {
echo "stream_select() に失敗しました\n";
}
?>
注意:
配列に返されたストリームに対して読み込みまたは書き込み操作を行う際に、 必ずしもあなたの希望しただけデータが読まれたり書かれたりはしないことに 注意してください。たった 1 バイトしか読み出せない場合も、書き込めない 場合もあるのです。
注意:
ストリームの中には、(
zlib
などのように) この関数で select できないものもあります。
注意: Windows の互換性
Windows では、proc_open() が返すファイル記述子に 対して stream_select() を使用すると 失敗して
false
を返すことがあります。あらゆる 入力イベントが利用可能になるとすぐに、 コンソールからの
STDIN
の状態も変化しますが、 ストリームからの読み出しはブロックされたままかもしれません。
参考
- stream_set_blocking() - ストリームのブロックモードを有効にする / 解除する
User Contributed Notes 12 notes
Note that reading from a regular file which is on end-of-file will *not* block. You'll get a non-blocking, zero-byte read. However, stream_select *will* block if the input is a pipe, and there is no more data to be had.
Maintaining connection with multiple clients can be tricky, PHP script is single-thread process, so if you like to do more than one thing at once (like waiting for new connections and waiting for new data), you’ll have to use some sort of multiplexing.
<?php
$socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
stream_set_blocking($socket, 0);
$connections = [];
$read = [];
$write = null;
$except = null;
while (1) {
// look for new connections
if ($c = @stream_socket_accept($socket, empty($connections) ? -1 : 0, $peer)) {
echo $peer.' connected'.PHP_EOL;
fwrite($c, 'Hello '.$peer.PHP_EOL);
$connections[$peer] = $c;
}
// wait for any stream data
$read = $connections;
if (stream_select($read, $write, $except, 5)) {
foreach ($read as $c) {
$peer = stream_socket_get_name($c, true);
if (feof($c)) {
echo 'Connection closed '.$peer.PHP_EOL;
fclose($c);
unset($connections[$peer]);
} else {
$contents = fread($c, 1024);
echo $peer.': '.trim($contents).PHP_EOL;
}
}
}
}
?>
If you want to set an absolute maximum execution time for stream_select in a loop, it's important to decrement the max_time value passed to stream_select.
<?php
// The maximum time for execution in milliseconds
$maxtime = 200000;
// The time the loop started
$starttime = microtime(true);
// Original array of sockets
$r = $orig_sockets;
// The function to calculate the timeout
function calctimeout($maxtime, $starttime)
{
return $maxtime - ((microtime(true) - $starttime) * 1000000);
}
while (stream_select($r, $w = null, $e = null, 0, calctimeout($maxtime, $starttime)) !== 0)
{
// loop through the sockets that showed activity
foreach ($r as $socket) {
// $socket talked
}
// stream_select modifies the contents of $r
// in a loop we should replace it with the original
$r = $orig_sockets;
}
?>
Note that you should change the calctimeout function below to divide the outcome by 1.000.000 otherwise you'll be waiting for two years instead of one minute for the socket to timeout...
<?php
// The function to calculate the timeout
function calctimeout($maxtime, $starttime)
{
return ($maxtime - ((microtime(true) - $starttime) * 1000000))/1000000;
}
?>
When stream_select() fails you SHOULD NOT use the results of the arrays (i.e. read, write, except) that were passed into it as parameters. While doing so anyway won't trigger undefined behaviour, you are depending on unspecified behaviour that by definition is not guaranteed.
At the time of writing the PHP 7.2 interpreter does not modify the arrays upon stream_select() failure (see the code around https://github.com/php/php-src/blob/php-7.2.14/ext/standard/streamsfuncs.c#L842) thus a PHP program that doesn't heed the advice above can deceive itself as to the state of those streams.
(Hopefully this warning can be added to the main documentation one day)
Make sure not to pass the same variable in the 3 arguments to stream_select, or you'll only get the results from one of them and the others will be overwritten.
If you're getting unexplainable problems with nonblocking sockets using stream_select, disable the buffers using:
stream_set_read_buffer($socket, 0);
stream_set_write_buffer($socket, 0);
For some reason when writing (in total) ~256k, sockets start returning FALSE when reading, yet always appear in the stream_select arrays. This fixed that problem. (for us.)
Please note that, on return, the key of "read" will be zero based, serially numbered according to the streams for which there is read data ready only. In other words, if you want to know which of the original streams placed in "read" is ready, there is no immediate way of knowing that.
If you want to know which of the original stream is which, you can either use "==", or possibly set a reverse map array, in which the stream is the key, and the key to the original "read" array is the data.
If you try to use stream_select() with fread(), you may get bit by a combination of bugs (https://bugs.php.net/bug.php?id=52602 and https://bugs.php.net/bug.php?id=51056). As of PHP 5.5.10, fread() and stream_select() don't reliably play well together.
If you need stream_select() and you don't need an encrypted connection (e.g. TLS), use stream_socket_recvfrom() instead of fread().
I can't find a way to reliably handle an encrypted connection with blocking functions in PHP; non-blocking might be the only way to do it.