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 — Supervisa la modificación de uno o varios flujos
&$read
,&$write
,&$except
,$seconds
,$microseconds
= null
stream_select() acepta un array de flujos y espera a que alguno de ellos cambie de estado. Esta operación es equivalente a lo que hace la función socket_select(), salvo que trabaja sobre un flujo.
read
Los flujos que están listados en el parámetro read
serán supervisados en lectura, es decir, si hay nuevos bytes
disponibles para lectura (para ser precisos, si una lectura no
bloqueará, lo que incluye también flujos que están al final de
archivo, en cuyo caso una llamada a la función fread()
retornará un string de tamaño 0).
write
Los flujos que están listados en el parámetro write
serán supervisados en escritura (para ser precisos, si una escritura no
bloqueará).
except
Los flujos que están listados en el parámetro except
serán supervisados para ver si se lanza una excepción.
Nota:
Cuando stream_select() termina, los arrays
read
,write
yexcept
son modificados para indicar qué flujos han cambiado de estado actualmente. Las claves originales del array se preservan.
seconds
Los parámetros seconds
y
microseconds
forman el tiempo límite,
seconds
especifica el número de segundos
mientras que microseconds
, el número de
microsegundos. El parámetro timeout
representa el límite superior del tiempo que
stream_select() debe esperar antes de
terminar. Si seconds
y
microseconds
están ambos definidos
a 0
, stream_select() no esperará
datos - en su lugar, terminará inmediatamente,
indicando el estado actual del flujo.
Si seconds
vale null
,
stream_select() puede bloquearse indefinidamente,
terminando únicamente cuando un evento en alguno de los flujos supervisados
ocurra (o si una señal interrumpe la llamada al sistema).
Utilizar un valor de 0
permite
probar instantáneamente el estado de los flujos, pero debe saberse
que no se recomienda utilizar 0
en un bucle, ya que esto hará que el script consuma una gran cantidad
de procesador.
Es mucho mejor especificar un valor de algunos segundos, incluso
si se debe supervisar y ejecutar diferentes códigos de manera
simultánea. Por ejemplo, utilizar un valor de al menos
200000
microsegundos, se reducirá considerablemente
el consumo de procesador del script.
No se debe olvidar que el valor de expiración es la duración máxima de espera, si no ocurre nada: stream_select() retornará un resultado tan pronto como uno de los flujos suministrados esté listo para su uso.
microseconds
Véase la descripción de seconds
.
En caso de éxito, stream_select() retorna
el número de flujos que han cambiado, lo que puede ser 0
, si
el tiempo límite fue alcanzado antes de que los flujos cambien.
En caso de error, la función retornará false
y un
aviso será devuelto (esto puede ocurrir si la llamada
al sistema es interrumpida por una señal entrante).
Versión | Descripción |
---|---|
8.1.0 |
microseconds ahora es nullable.
|
Ejemplo #1 Ejemplo con stream_select()
Este ejemplo supervisa si los datos llegan para ser
leídos ya sea en $stream1
o en
$stream2
. Si el tiempo límite
es 0
, la función termina inmediatamente:
<?php
/* Preparación del array de flujos de lectura */
$read = array($stream1, $stream2);
$write = NULL;
$except = NULL;
if (false === ($num_changed_streams = stream_select($read, $write, $except, 0))) {
/* Manejo de errores */
} elseif ($num_changed_streams > 0) {
/* Al menos uno de los flujos ha cambiado */
}
?>
Nota:
Debido a una limitación del motor Zend actual, no es posible pasar el valor
null
directamente como parámetro de una función que espera parámetros pasados por referencia. En su lugar, se recomienda utilizar una variable temporal, o una expresión cuyo miembro izquierdo sea una variable temporal. Como esto:<?php
$e = NULL;
stream_select($r, $w, $e, 0);
?>
Nota:
Asegúrese de utilizar el operador
===
cuando busque errores. Como stream_select() puede retornar 0, una comparación realizada con==
lo evaluaría comotrue
:<?php
$e = NULL;
if (false === stream_select($r, $w, $e, 0)) {
echo "stream_select() falló\n";
}
?>
Nota:
Si ha escrito o leído en un flujo que es retornado en los arrays de flujos, sea consciente de que estos flujos pueden no haber escrito o leído la totalidad de los datos solicitados. Sea capaz de leer un solo byte.
Nota:
Algunos flujos (como
zlib
) no pueden ser seleccionados por esta función.
Nota: Compatibilidad con Windows
Utilizar la función stream_select() en un puntero de archivo retornado por proc_open() fallará y retornará
false
en Windows.
STDIN
desde una consola cambia su estado tan pronto como cualquier evento de entrada esté disponible, pero leer desde un flujo puede seguir siendo bloqueante.
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.
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)
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;
}
?>
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.
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.
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.