Ejecutar proceso con salida en tiempo real en PHP


Estoy tratando de ejecutar un proceso en una página web que devolverá su salida en tiempo real. Por ejemplo, si corro 'ping' process debería actualizar mi página cada vez que devuelve una nueva línea(ahora mismo, cuando uso exec (comando, salida) me veo obligado a usar la opción-c y esperar hasta que el proceso termine para ver la salida en mi página web). Es posible hacer esto en php?

También me pregunto cuál es una manera correcta de matar este tipo de proceso cuando alguien está saliendo de la página. En caso de "ping" proceso Todavía puedo ver el proceso que se ejecuta en el monitor del sistema (lo que tiene sentido).

Author: Maksim Vi., 2009-08-15

11 answers

Esto funcionó para mí:

$cmd = "ping 127.0.0.1";

$descriptorspec = array(
   0 => array("pipe", "r"),   // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),   // stdout is a pipe that the child will write to
   2 => array("pipe", "w")    // stderr is a pipe that the child will write to
);
flush();
$process = proc_open($cmd, $descriptorspec, $pipes, realpath('./'), array());
echo "<pre>";
if (is_resource($process)) {
    while ($s = fgets($pipes[1])) {
        print $s;
        flush();
    }
}
echo "</pre>";
 79
Author: egafni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-07 22:42:01

Esta es una buena manera de mostrar la salida en tiempo real de sus comandos de shell:

<?php
header("Content-type: text/plain");

// tell php to automatically flush after every output
// including lines of output produced by shell commands
disable_ob();

$command = 'rsync -avz /your/directory1 /your/directory2';
system($command);

Necesitará esta función para evitar el almacenamiento en búfer de salida:

function disable_ob() {
    // Turn off output buffering
    ini_set('output_buffering', 'off');
    // Turn off PHP output compression
    ini_set('zlib.output_compression', false);
    // Implicitly flush the buffer(s)
    ini_set('implicit_flush', true);
    ob_implicit_flush(true);
    // Clear, and turn off output buffering
    while (ob_get_level() > 0) {
        // Get the curent level
        $level = ob_get_level();
        // End the buffering
        ob_end_clean();
        // If the current level has not changed, abort
        if (ob_get_level() == $level) break;
    }
    // Disable apache output buffering/compression
    if (function_exists('apache_setenv')) {
        apache_setenv('no-gzip', '1');
        apache_setenv('dont-vary', '1');
    }
}

No funciona en todos los servidores en los que lo he probado, sin embargo, me gustaría poder ofrecer consejos sobre qué buscar en su configuración de php para determinar si debe o no tirarse del pelo tratando de que este tipo de comportamiento funcione en su servidor! Nadie sabe?

Aquí hay un ejemplo ficticio en plain PHP:

<?php
header("Content-type: text/plain");

disable_ob();

for($i=0;$i<10;$i++) 
{
    echo $i . "\n";
    usleep(300000);
}

Espero que esto ayude a otros que han buscado en Google su camino aquí.

 35
Author: Robb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-07 22:44:22

Prueba esto (probado en máquina Windows + servidor wamp)

        header('Content-Encoding: none;');

        set_time_limit(0);

        $handle = popen("<<< Your Shell Command >>>", "r");

        if (ob_get_level() == 0) 
            ob_start();

        while(!feof($handle)) {

            $buffer = fgets($handle);
            $buffer = trim(htmlspecialchars($buffer));

            echo $buffer . "<br />";
            echo str_pad('', 4096);    

            ob_flush();
            flush();
            sleep(1);
        }

        pclose($handle);
        ob_end_flush();
 5
Author: raminious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-15 17:05:23

Aquí se describe una mejor solución a este viejo problema utilizando eventos secundarios del servidor HTML5 moderno:

Http://www.w3schools.com/html/html5_serversentevents.asp


Ejemplo:

Http://sink.agiletoolkit.org/realtime/console

Código: https://github.com/atk4/sink/blob/master/admin/page/realtime/console.php#L40

(Implementado como un módulo en Agile Toolkit framework)

 5
Author: romaninsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-04 18:04:37

He probado varios comandos de ejecución PHP en Windows y he encontrado que difieren bastante.

  • No funciona para la transmisión: shell_exec, exec, passthru
  • Tipo de obras: proc_open, popen -- "tipo de" porque no puede pasar argumentos a su comando (es decir, no funcionará con my.exe --something, funcionará con _my_something.bat).

El mejor enfoque (más fácil) es:

  1. Debe asegurarse de que su exe esté descargando los comandos (vea problema de descarga de printf). Sin esto lo más probable es que reciba lotes de aproximadamente 4096 bytes de texto, haga lo que haga.
  2. Si puede, use header('Content-Type: text/event-stream'); (en lugar de header('Content-Type: text/plain; charset=...');). Esto no funcionará en todos los navegadores/clientes sin embargo! La transmisión funcionará sin esto, pero al menos las primeras líneas serán guardadas por el navegador.
  3. También es posible que desee desactivar la caché header('Cache-Control: no-cache');.
  4. Desactiva el búfer de salida (ya sea en php.ini o con ini_set('output_buffering', 'off');). Esto también puede tener que hacerse en Apache / Nginx / cualquier servidor que utilice en delantero.
  5. Turno de compresión (ya sea en php.ini o con ini_set('zlib.output_compression', false);). Esto también podría tener que hacerse en Apache/Nginx/cualquier servidor que utilice en frente.

Así que en su programa de C++ que hacer algo como (de nuevo, para otras soluciones ver problema de descarga printf):

Logger::log(...) {
  printf (text);
  fflush(stdout);
}

En PHP haces algo como:

function setupStreaming() {
    // Turn off output buffering
    ini_set('output_buffering', 'off');
    // Turn off PHP output compression
    ini_set('zlib.output_compression', false);
    // Disable Apache output buffering/compression
    if (function_exists('apache_setenv')) {
        apache_setenv('no-gzip', '1');
        apache_setenv('dont-vary', '1');
    }
}
function runStreamingCommand($cmd){
    echo "\nrunning $cmd\n";
    system($cmd);
}
...

setupStreaming();
runStreamingCommand($cmd);
 3
Author: Nux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:39

Comprobado todas las respuestas, nada funciona...

Se encontró la solución Aquí

Funciona en Windows (creo que esta respuesta es útil para los usuarios que buscan allí)

<?php
$a = popen('ping www.google.com', 'r'); 

while($b = fgets($a, 2048)) { 
echo $b."<br>\n"; 
ob_flush();flush(); 
} 
pclose($a); 

?>
 3
Author: ვანი ჩკაა,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-07 16:37:51

Primero compruebe si flush() funciona para usted. Si lo hace, bien, si no lo hace probablemente significa que el servidor web se está almacenando en búfer por alguna razón, por ejemplo mod_gzip está habilitado.

Para algo como ping, la técnica más fácil es hacer un bucle dentro de PHP, ejecutando "ping-c 1" varias veces, y llamando a flush() después de cada salida. Suponiendo que PHP está configurado para abortar cuando la conexión HTTP es cerrada por el usuario (que suele ser el valor predeterminado, o puede llamar a ignore_user_abort (false) para asegurarse), entonces tampoco necesita preocuparse por los procesos de ping fugitivos.

Si es realmente necesario que solo ejecute el proceso hijo una vez y muestre su salida continuamente, eso puede ser más difícil probably probablemente tendría que ejecutarlo en segundo plano, redirigir la salida a un flujo, y luego hacer que PHP haga eco de ese flujo de vuelta al usuario, intercalado con llamadas regulares a flush ().

 2
Author: Todd Owen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-15 05:14:40

Si está buscando ejecutar comandos del sistema a través de PHP, busque la documentación exec.

No recomendaría hacer esto en un sitio de alto tráfico, sin embargo, bifurcar un proceso para cada solicitud es un proceso bastante fuerte. Algunos programas ofrecen la opción de escribir su id de proceso en un archivo tal que se pueda comprobar y terminar el proceso a voluntad, pero para comandos como ping, no estoy seguro de que sea posible, compruebe las páginas de manual.

Usted puede ser mejor servido por simplemente abrir un socket en el puerto que espera escuchar (es decir, el puerto 80 para HTTP) en el host remoto, de esa manera sabe que todo va bien en el país de usuario, así como en la red.

Si está intentando generar datos binarios, busque en la función de encabezado de php, y asegúrese de establecer el content-type y content-disposition. Revise la documentación, para obtener más información sobre cómo usar/deshabilitar el búfer de salida.

 2
Author: Nathacof,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-15 06:54:55

Para el uso de la línea de comandos:

function execute($cmd) {
    $proc = proc_open($cmd, [['pipe','r'],['pipe','w'],['pipe','w']], $pipes);
    while(($line = fgets($pipes[1])) !== false) {
        fwrite(STDOUT,$line);
    }
    while(($line = fgets($pipes[2])) !== false) {
        fwrite(STDERR,$line);
    }
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    return proc_close($proc);
}

Si está intentando ejecutar un archivo, es posible que deba darle permisos de ejecución primero:

chmod('/path/to/script',0755);
 2
Author: mpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-15 17:49:30

Intente cambiar el php.el archivo ini establece "output_buffering = Off". Usted debe ser capaz de obtener la salida en tiempo real en la página Utilice el comando system en lugar de exec.. el comando del sistema descargará la salida

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-15 20:38:14

¿Por qué no canalizar la salida a un archivo de registro y luego usar ese archivo para devolver el contenido al cliente? no en tiempo real, pero tal vez lo suficientemente bueno?

 0
Author: greenone83,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-09 15:28:40