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.
(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)
stream_select — select() システムコールと同等の操作を、 ストリームの配列に対して seconds と microseconds で指定されたタイムアウト時間をもって行う
&$read
,&$write
,&$except
,$seconds
,$microseconds
= null
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
の状態も変化しますが、 ストリームからの読み出しはブロックされたままかもしれません。
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.
You can key on file descriptors just fine by casting them to an int or a string, which returns what you would expect.
If you use stream_select() with a blocking stream, you are doing it wrong!
Just because this function returns something in one or more of the arrays does NOT mean that a future read or write operation will not block.
The above sentence is the most important sentence you will ever read regarding stream manipulation. Using stream_select() with blocking streams is a very common amateur mistake and causes major headaches when tracking down usage of this and similar select() system functions. PHP (and really the underlying OS) should verify that the supplied stream set is not blocking and throw an error/exception if any socket is set to block so that people are forced to fix their code. The documentation for stream_select() is, at best, misleading.
If you want a non-blocking stream, then set the stream to not block. Otherwise, live with the blocking stream. That is, after all, the whole point of blocking - to block indefinitely until the operation completes. select() is built for non-blocking streams ONLY. Any other use will result in very hard to track down bugs.
I got the above lecture many years ago after encountering the very bugs I mention. I fixed my code and now correct similar mistakes when I run into the issue elsewhere. Writing code for non-blocking streams is simpler than trying to write hacks for blocking streams with select() functions and ending up with application bugs.
stream_select() looks deceptively like a simple wrapper around POSIX select(2).
But beware: while select(2) allows you to pass no file descriptors and use it as a "portable subsecond sleep", PHP will complain with "Warning: stream_select(): No stream arrays were passed in ****" if all arrays are empty or null, and it WONT sleep, it will return immediately. So... if the number of file descriptors you have isn't static, you have to deal with the special case yourself.