PHPのお勉強!

PHP TOP

stream_select

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

stream_selectselect() システムコールと同等の操作を、 ストリームの配列に対して seconds と microseconds で指定されたタイムアウト時間をもって行う

説明

stream_select(
    ?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() の終了時には、 どのストリームの状態が実際に変化したのかが分かるよう、 配列 readwrite および except に変更が加えられます。 但し、配列のキーは保存されます。

seconds

secondsmicroseconds は、一体となって、timeout パラメータを表現します。 seconds は秒数を指定し、一方 microseconds はマイクロ秒数を指定します。 timeout は、stream_select() の実行から戻るまでの時間の上限です。 seconds および microseconds の両方に 0 を指定すると stream_select() はデータを待たずに一瞬で戻ります。 これは現在のストリームの状態を示します。

secondsnull (タイムアウトなし) の場合、 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 の状態も変化しますが、 ストリームからの読み出しはブロックされたままかもしれません。

参考

add a note

User Contributed Notes 12 notes

up
4
php at richardneill dot org
15 years ago
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.
up
5
Martin
6 years ago
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;
}
}
}
}
?>
up
5
aidan at php dot net
20 years ago
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;
}

?>
up
6
maartenwolzak at gmail dot com
17 years ago
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;
}

?>
up
4
Sitsofe
5 years ago
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)
up
4
asphp at dsgml dot com
8 years ago
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.
up
3
mark2xv at gmail dot com
11 years ago
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.)
up
3
phpdoc at shemesh dot biz
19 years ago
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.
up
2
rob at associatedtechs dot com
10 years ago
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.
up
2
Ben
17 years ago
You can key on file descriptors just fine by casting them to an int or a string, which returns what you would expect.