PHP ejecuta un proceso en segundo plano


259

Necesito ejecutar una copia de directorio en una acción del usuario, pero los directorios son bastante grandes, por lo que me gustaría poder realizar tal acción sin que el usuario sepa el tiempo que tarda la copia en completarse.

Cualquier sugerencia sería muy apreciada.


3
Este stackoverflow.com/questions/5367261/… explica cómo hacer esto bajo windows
shealtiel

Respuestas:


365

Suponiendo que esto se esté ejecutando en una máquina Linux, siempre lo he manejado así:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

Esto inicia el comando $cmd, redirige la salida del comando $outputfiley escribe la identificación del proceso $pidfile.

Eso le permite monitorear fácilmente lo que está haciendo el proceso y si aún se está ejecutando.

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

2
¿La configuración de sudo se ejecuta sin solicitar una contraseña? Cualquier comando que requiera la entrada del usuario no funcionará.
Mark Biek

17
Este stackoverflow.com/questions/5367261/… explica cómo hacer lo mismo bajo Windows
shealtiel

13
Usé esto, pero lo actualicé un poco para no tener que escribir el PID en un archivo. Entonces uso este formato: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> El PID del proceso resultante está en $ pidArr [0]
Kaiesh

3
@Kaiesh: debería ser "echo $!"
brunobg

8
Kaiesh hizo otro error tipográfico, '$' en lugar de '%'. Versión corregida: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor

23

Escriba el proceso como un script del lado del servidor en cualquier idioma (php / bash / perl / etc) que sea útil y luego instálelo desde las funciones de control de proceso en su script php.

La función probablemente detecta si se usa io estándar como flujo de salida y si es así, eso establecerá el valor de retorno ... si no, entonces termina

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

Probé esto rápidamente desde la línea de comando usando "sleep 25s" como comando y funcionó de maravilla.

( Respuesta encontrada aquí )


3
Esta es la única forma en que podría hacer que funcione en centos. ¡Gracias!
Deac Karns

Gracias ! Hay una incompatibilidad de PHP y BASH. Si ejecuta un sistema más nuevo, no se iniciará en segundo plano utilizando system () o exec (). Los descriptores de archivo parecen bloquearse incluso si redirige. Tu método funcionó. otra alternativa es usar un viejo binario de bash para PHP, pero eso es una locura
John

22

Es posible que desee intentar agregar esto a su comando

>/dev/null 2>/dev/null &

p.ej.

shell_exec('service named reload >/dev/null 2>/dev/null &');


Es trabajo para mí, y utilizaré el archivo MAKE para hacer algo en el sitio del servidor, ¡gracias! (ej. hacer, hacer reiniciar)
Chu-Siang Lai

¡Esto es mucho más simple que la respuesta aceptada! (Mientras no necesite nada más complejo, como verificar si el proceso aún se está ejecutando.)
Sean the Bean

18

Solo me gustaría agregar un ejemplo muy simple para probar esta funcionalidad en Windows:

Cree los siguientes dos archivos y guárdelos en un directorio web:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

Otorgue permiso a IUSR para escribir en el directorio en el que creó los archivos anteriores

Otorgue permiso a IUSR para LEER y EJECUTAR C: \ Windows \ System32 \ cmd.exe

Presiona foreground.php desde un navegador web

Lo siguiente debe representarse en el navegador con las marcas de tiempo actuales y el número de recurso local en la matriz de salida:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

Debería ver testoutput.php en el mismo directorio donde se guardaron los archivos anteriores, y debería estar vacío

Debería ver testprocesses.php en el mismo directorio donde se guardaron los archivos anteriores, y debe contener el siguiente texto con las marcas de tiempo actuales:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

10

Si necesita hacer algo en segundo plano sin que la página PHP espere a que se complete, puede usar otro script PHP (en segundo plano) que se "invoque" con el comando wget. Este script PHP en segundo plano se ejecutará con privilegios, por supuesto, como cualquier otro script PHP en su sistema.

Aquí hay un ejemplo en Windows usando wget de paquetes gnuwin32.

El código de fondo (archivo test-proc-bg.php) como un ejemplo ...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

El guión en primer plano, el que invoca ...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

Debe usar popen / pclose para que esto funcione correctamente.

Las opciones de wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

7

Aquí hay una función para iniciar un proceso en segundo plano en PHP. Finalmente creé uno que también funciona en Windows, después de leer y probar diferentes enfoques y parámetros.

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

Nota 1: en Windows, no use el /Bparámetro como se sugiere en otra parte. Obliga al proceso a ejecutar la misma ventana de consola que el startcomando mismo, lo que da como resultado que el proceso se procese sincrónicamente. Para ejecutar el proceso en un hilo separado (asincrónicamente), no use/B .

Nota 2: las comillas dobles vacías después start ""son necesarias si el comando es una ruta entre comillas. startEl comando interpreta el primer parámetro citado como el título de la ventana.


6

Bueno, encontré una versión un poco más rápida y fácil de usar

shell_exec('screen -dmS $name_of_screen $command'); 

y funciona.


@ Alpha.Dev Lo probé solo en Linux. No sé si funcionará en otro sistema.
Morfidon

4

¿Puede organizar la bifurcación de un proceso separado y luego ejecutar su copia en segundo plano? Ha pasado un tiempo desde que hice cualquier PHP, pero la función pcntl-fork parece prometedora.


Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación.
Rizier123

13
Gracias - Los comentarios no existían en el '08, pero lo tendré en cuenta :)
Matt Sheppard

4

Use esta función para ejecutar su programa en segundo plano. Es multiplataforma y totalmente personalizable.

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

Tenga en cuenta que después de que se inicia el comando, por defecto esta función cierra el stdin y el stdout del proceso en ejecución. Puede redirigir la salida del proceso a algún archivo mediante los argumentos $ redirectStdout y $ redirectStderr.

Nota para usuarios de Windows:
no puede redirigir stdout / stderr de nulla siguiente manera:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

Sin embargo, puedes hacer esto:

startBackgroundProcess('ping yandex.com >nul 2>&1');

Notas para usuarios de * nix:

1) Use el comando de shell exec si desea obtener el PID real:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) Use el argumento $ stdin si desea pasar algunos datos a la entrada de su programa:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

3

Puede probar un sistema de colas como Resque . Luego puede generar un trabajo, que procesa la información y retorna bastante rápido con la imagen de "procesamiento". Sin embargo, con este enfoque no sabrá cuándo está terminado.

Esta solución está diseñada para aplicaciones a gran escala, donde no desea que sus máquinas frontales realicen el trabajo pesado, para que puedan procesar las solicitudes de los usuarios. Por lo tanto, podría o no funcionar con datos físicos como archivos y carpetas, pero para procesar lógica más complicada u otras tareas asincrónicas (es decir, nuevos correos de registros) es bueno tenerlo y es muy escalable.


2

Una solución de trabajo para Windows y Linux. Encuentre más en mi página de github .

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }


1

En lugar de iniciar un proceso en segundo plano, ¿qué pasa con la creación de un archivo desencadenante y tener un programador como cron o autosys ejecutar periódicamente un script que busca y actúa en los archivos desencadenantes? Los desencadenantes pueden contener instrucciones o incluso comandos sin formato (mejor aún, solo conviértalo en un script de shell).



1

Estoy usando mucho fast_cgi_finish_request()

En combinación con un cierre y register_shutdown_function ()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

Luego registre este cierre para que se ejecute antes del apagado.

register_shutdown_function($backgroundJob);

Finalmente, cuando se envió la respuesta al cliente, puede cerrar la conexión con el cliente y continuar trabajando con el proceso de PHP:

fast_cgi_finish_request();

El cierre se ejecutará después de fast_cgi_finish_request.

El mensaje $ no estará visible en ningún momento. Y puede registrar tantos cierres como desee, pero tenga cuidado con el tiempo de ejecución del script. Esto solo funcionará si PHP se está ejecutando como un módulo Fast CGI (¡¿verdad ?!)


0

El script PHP no es como otro lenguaje de desarrollo de aplicaciones de escritorio. En los lenguajes de aplicaciones de escritorio, podemos configurar hilos de daemon para ejecutar un proceso en segundo plano, pero en PHP se produce un proceso cuando el usuario solicita una página. Sin embargo, es posible establecer un trabajo en segundo plano utilizando la funcionalidad de trabajo cron del servidor que ejecuta el script php.


0

Para aquellos de nosotros que usamos Windows , mira esto:

Referencia: http://php.net/manual/en/function.exec.php#43917

Yo también luché para que un programa se ejecutara en segundo plano en Windows mientras el script continúa ejecutándose. Este método, a diferencia de las otras soluciones, le permite iniciar cualquier programa minimizado, maximizado o sin ninguna ventana. La solución de llbra @ phpbrasil funciona, pero a veces produce una ventana no deseada en el escritorio cuando realmente desea que la tarea se ejecute oculta.

iniciar Notepad.exe minimizado en segundo plano:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

iniciar un comando de shell invisible en segundo plano:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

inicie MSPaint maximizado y espere a que lo cierre antes de continuar el script:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

Para obtener más información sobre el método Run (), visite: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

URL editada:

Vaya a https://technet.microsoft.com/en-us/library/ee156605.aspx, ya que el enlace de arriba ya no existe.


1
¿Qué se debe hacer para ejecutar este código? ObtengoFatal error: Class 'COM' not found
Alph.Dev

Ps Link to MSDN is broken
Alph.Dev

@ Alph.Dev: el documento msdn parece haberse eliminado temporalmente. Contenía más ejemplos y explicaciones sobre cómo usar WScript.Shell.
Jimmy Ilenloa

@ Alph.Dev: Para la clase COM no encontrada, eso es otra cosa. Mira eso en google. También puede consultar esto php.net/manual/en/com.installation.php
Jimmy Ilenloa

@ Alph.Dev, consulte technet.microsoft.com/en-us/library/ee156605.aspx para obtener información actualizada sobre la función Run ()
Jimmy Ilenloa

-12

Sé que es una publicación de 100 años, pero de todos modos, pensé que podría ser útil para alguien. Puede colocar una imagen invisible en algún lugar de la página que apunte a la URL que debe ejecutarse en segundo plano, de esta manera:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />


12
Muy mala solución. Si el usuario abandonara la página, el proceso se interrumpirá.
Sergey P. aka azure

3
la "imagen invisible" y su enlace están expuestos en el código fuente de la página.
Tony Gil
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.