web-dev-qa-db-fra.com

Exécuter le processus avec une sortie en temps réel dans PHP

J'essaie d'exécuter un processus sur une page Web qui renverra sa sortie en temps réel. Par exemple, si je lance un processus 'ping', il doit mettre à jour ma page chaque fois qu'il renvoie une nouvelle ligne (maintenant, lorsque j'utilise exec (commande, sortie), je suis obligé d'utiliser l'option -c et d'attendre la fin du processus pour voir sortie sur ma page web). Est-il possible de faire cela en php?

Je me demande également quelle est la bonne façon de supprimer ce type de processus lorsque quelqu'un quitte la page. En cas de processus "ping", je suis toujours en mesure de voir le processus en cours d'exécution dans le moniteur système (ce qui est logique).

62
Maksim Vi.

Cela a fonctionné pour moi:

$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>";
85
egafni

C’est un bon moyen d’afficher la sortie en temps réel de vos commandes 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);

Vous aurez besoin de cette fonction pour empêcher la mise en mémoire tampon de sortie:

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');
    }
}

Cela ne fonctionne pas sur tous les serveurs que j'ai essayés, cependant, j'aimerais pouvoir vous donner des conseils sur ce qu'il faut rechercher dans votre configuration php afin de déterminer si vous devez ou non vous arracher les cheveux pour essayer d'obtenir ce type de comportement au travail sur votre serveur! Quelqu'un d'autre sait?

Voici un exemple factice en PHP simple:

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

disable_ob();

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

J'espère que cela aidera les autres qui ont cherché sur Google.

35
Robb

Une meilleure solution à ce vieux problème en utilisant les événements HTML5 Server Side modernes est décrite ici:

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


Exemple:

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

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

(Implémenté en tant que module dans le framework Agile Toolkit)

6
romaninsh

Vérifié toutes les réponses, rien ne fonctionne ...

Solution trouvée Ici

Cela fonctionne sur les fenêtres (je pense que cette réponse est utile pour les utilisateurs qui cherchent là-bas)

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

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

?>
4
Pixsa

essayez ceci (testé sur machine Windows + serveur 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();
4
raminious

J'ai essayé diverses PHP - commandes d'exécution sous Windows et j'ai constaté qu'elles étaient très différentes.

  • Ne fonctionne pas pour la diffusion: Shell_exec, exec, passthru
  • Type de travaux: proc_open, popen - "type de" parce que vous ne pouvez pas passer d'arguments à votre commande (c'est-à-dire que wont 'travailler avec my.exe --something, fonctionnera avec _my_something.bat).

La meilleure approche (la plus facile) est:

  1. Vous devez vous assurer que votre exe exécute les commandes (voir printf flushing problem ). Sans cela, vous recevrez probablement des lots d'environ 4096 octets de texte, quelle que soit votre activité.
  2. Si vous le pouvez, utilisez header('Content-Type: text/event-stream'); (au lieu de header('Content-Type: text/plain; charset=...');). Cela ne fonctionnera pas dans tous les navigateurs/clients cependant! La diffusion en continu fonctionnera sans cela, mais au moins les premières lignes seront mises en mémoire tampon par le navigateur.
  3. Vous voudrez peut-être également désactiver le cache header('Cache-Control: no-cache');.
  4. Désactivez la mise en tampon de sortie (dans php.ini ou avec ini_set('output_buffering', 'off');). Cela devra peut-être aussi être fait dans Apache/Nginx/quel que soit le serveur que vous utilisez devant.
  5. Désactivation de la compression (dans le fichier php.ini ou avec ini_set('zlib.output_compression', false);). Cela devra peut-être aussi être fait dans Apache/Nginx/quel que soit le serveur que vous utilisez devant.

Donc, dans votre programme C++, vous faites quelque chose comme (encore une fois, pour d'autres solutions, voir problème de vidage printf ):

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

Dans PHP vous faites quelque chose comme:

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
Nux

Si vous souhaitez exécuter des commandes système via PHP, examinez la documentation exec .

Je ne recommanderais cependant pas de faire cela sur un site à fort trafic, mais imposer un processus pour chaque demande est un processus très lourd. Certains programmes offrent la possibilité d'écrire leur identifiant de processus dans un fichier permettant de vérifier et de terminer le processus à volonté, mais pour les commandes telles que ping, je ne suis pas sûr que ce soit possible. Consultez les pages de manuel.

Vous serez peut-être mieux servi en ouvrant simplement un socket sur le port que vous vous attendez à écouter (IE: port 80 pour HTTP) sur l'hôte distant, de cette manière, vous savez que tout se passe bien, tant dans le pays utilisateur que sur le réseau.

Si vous essayez de produire des données binaires, cherchez fonction d'en-tête de php , et assurez-vous de définir le paramètre correct content-type , et content-disposition . Consultez la documentation , pour plus d'informations sur l'utilisation/la désactivation du tampon de sortie.

2
Nathacof

Commencez par vérifier si flush () fonctionne pour vous. Si c'est le cas, bien, sinon, cela signifie probablement que le serveur Web met la mémoire tampon en mémoire tampon pour une raison quelconque, par exemple, mod_gzip est activé.

Pour quelque chose comme le ping, la technique la plus simple consiste à effectuer une boucle dans PHP, en exécutant "ping -c 1" plusieurs fois et en appelant flush () après chaque sortie. En supposant que PHP est configuré pour abandonner lorsque la connexion HTTP est fermée par l'utilisateur (ce qui est généralement la valeur par défaut, ou vous pouvez appeler ignore_user_abort (false) pour vous en assurer), vous n'avez donc pas à vous soucier de l'exécution. processus ping loin soit.

S'il est vraiment nécessaire de n'exécuter que le processus enfant une fois et d'afficher sa sortie en continu, cela risque d'être plus difficile: vous devrez probablement l'exécuter en arrière-plan, rediriger la sortie vers un flux, puis PHP renvoie ce flux à l'utilisateur, entrecoupé d'appels classiques.

2
Todd Owen

Pour une utilisation en ligne de commande:

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 vous essayez d'exécuter un fichier, vous devrez peut-être d'abord lui donner des autorisations d'exécution:

chmod('/path/to/script',0755);
2
mpen

Essayez de changer le jeu de fichiers php.ini "output_buffering = Off". Vous devriez pouvoir obtenir la sortie en temps réel sur la page Utiliser la commande système au lieu de la commande exec ..

0
Yatin

pourquoi ne pas simplement diriger la sortie dans un fichier journal, puis l’utiliser pour renvoyer le contenu au client. pas tout à fait en temps réel mais peut-être assez bon?

0
greenone83