PHP führen einen Hintergrundprozess aus

259

Ich muss eine Verzeichniskopie für eine Benutzeraktion ausführen, aber die Verzeichnisse sind ziemlich groß. Daher möchte ich eine solche Aktion ausführen können, ohne dass der Benutzer weiß, wie lange es dauert, bis die Kopie abgeschlossen ist.

Anregungen wäre sehr dankbar.

tim
quelle
3
Diese stackoverflow.com/questions/5367261/… erklärt, wie man das unter Windows macht
Shealtiel

Antworten:

365

Angenommen, dies läuft auf einem Linux-Computer, habe ich es immer so gehandhabt:

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

Dadurch wird der Befehl gestartet, die Befehlsausgabe $cmdumgeleitet $outputfileund die Prozess-ID in geschrieben $pidfile.

Auf diese Weise können Sie einfach überwachen, was der Prozess tut und ob er noch ausgeführt wird.

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;
}
Mark Biek
quelle
2
Soll das sudo-Setup ohne Aufforderung zur Eingabe eines Kennworts ausgeführt werden? Alle Befehle, die Benutzereingaben erfordern, funktionieren nicht.
Mark Biek
17
Diese stackoverflow.com/questions/5367261/… erklärt, wie man das gleiche unter Windows macht
Shealtiel
13
Ich habe dies verwendet, es aber leicht aktualisiert, damit ich die PID nicht in eine Datei schreiben musste. Also benutze ich dieses Format: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </ code> Die resultierende Prozess-PID ist in $ pidArr [0]
Kaiesh
3
@ Kaiesh: Es sollte "echo $!" Sein.
Brunobg
8
Kaiesh machte einen weiteren Tippfehler, '$' anstelle von '%'. Korrigierte Version: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor
23

Schreiben Sie den Prozess als serverseitiges Skript in einer beliebigen Sprache (php / bash / perl / etc) und rufen Sie ihn dann über die Prozesssteuerungsfunktionen in Ihrem PHP-Skript auf.

Die Funktion erkennt wahrscheinlich, ob Standard-Io als Ausgabestream verwendet wird, und wenn dies der Fall ist, wird der Rückgabewert festgelegt. Wenn nicht, endet er

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

Ich habe dies schnell über die Befehlszeile mit "sleep 25s" als Befehl getestet und es hat wie ein Zauber funktioniert.

( Antwort hier gefunden )

Eric Goodwin
quelle
3
Nur so kann ich es für Centos einsetzen. Danke dir!
Deac Karns
Vielen Dank ! Es gibt eine Inkompatibilität von PHP und BASH. Wenn Sie ein neueres System ausführen, kann es nicht mit system () oder exec () im Hintergrund gestartet werden. Die Dateideskriptoren scheinen selbst dann hängen zu bleiben, wenn Sie umleiten. Ihre Methode hat funktioniert. Eine andere Alternative ist die Verwendung einer alten Bash-Binärdatei für PHP, aber das ist verrückt
John
22

Möglicherweise möchten Sie versuchen, dies an Ihren Befehl anzuhängen

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

z.B.

shell_exec('service named reload >/dev/null 2>/dev/null &');
wlf
quelle
Ich meinte> / dev / null 2> / dev / null &
wlf
Es funktioniert für mich und ich werde das Makefile verwenden, um etwas auf der Server-Site zu tun, danke! (zB machen, neu starten)
Chu-Siang Lai
Dies ist viel einfacher als die akzeptierte Antwort! (Solange Sie nichts Komplexeres brauchen, wie zu überprüfen, ob der Prozess noch läuft.)
Sean the Bean
18

Ich möchte nur ein sehr einfaches Beispiel zum Testen dieser Funktionalität unter Windows hinzufügen:

Erstellen Sie die folgenden zwei Dateien und speichern Sie sie in einem Webverzeichnis:

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);
?>

Geben Sie IUSR die Berechtigung, in das Verzeichnis zu schreiben, in dem Sie die obigen Dateien erstellt haben

Geben Sie IUSR die Berechtigung zum Lesen und Ausführen von C: \ Windows \ System32 \ cmd.exe

Klicken Sie in einem Webbrowser auf foreground.php

Folgendes sollte für den Browser mit den aktuellen Zeitstempeln und der lokalen Ressource # im Ausgabearray gerendert werden:

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

Sie sollten testoutput.php im selben Verzeichnis sehen, in dem die oben genannten Dateien gespeichert wurden, und es sollte leer sein

Sie sollten testprocesses.php im selben Verzeichnis sehen, in dem die oben genannten Dateien gespeichert wurden, und es sollte den folgenden Text mit den aktuellen Zeitstempeln enthalten:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610
Kapitän offensichtlich
quelle
10

Wenn Sie nur etwas im Hintergrund tun müssen, ohne dass die PHP-Seite darauf wartet, dass es abgeschlossen wird, können Sie ein anderes (Hintergrund-) PHP-Skript verwenden, das mit dem Befehl wget "aufgerufen" wird. Dieses Hintergrund-PHP-Skript wird natürlich wie jedes andere PHP-Skript auf Ihrem System mit Berechtigungen ausgeführt.

Hier ist ein Beispiel unter Windows mit wget aus gnuwin32-Paketen.

Der Hintergrundcode (Datei test-proc-bg.php) als Beispiel ...

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

Das Vordergrundskript, das ...

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

Sie müssen popen / pclose verwenden, damit dies ordnungsgemäß funktioniert.

Die wget Optionen:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background
Neven Boyanov
quelle
7

Hier ist eine Funktion zum Starten eines Hintergrundprozesses in PHP. Schließlich wurde eine erstellt, die auch unter Windows funktioniert, nachdem viele verschiedene Ansätze und Parameter gelesen und getestet wurden.

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

Hinweis 1: Verwenden Sie unter Windows keine /BParameter, wie an anderer Stelle vorgeschlagen. Der Prozess wird gezwungen, dasselbe Konsolenfenster wie der startBefehl selbst auszuführen , was dazu führt, dass der Prozess synchron verarbeitet wird. Verwenden Sie nicht, um den Prozess in einem separaten Thread (asynchron) auszuführen /B.

Hinweis 2: Die leeren doppelten Anführungszeichen danach start ""sind erforderlich, wenn der Befehl ein Pfad in Anführungszeichen ist. startBefehl interpretiert den ersten zitierten Parameter als Fenstertitel.

Alph.Dev
quelle
6

Nun, ich fand eine etwas schnellere und einfachere Version

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

und es funktioniert.

Morfidon
quelle
@ Alpha.Dev Ich habe es nur unter Linux getestet. Ich weiß nicht, ob es auf einem anderen System funktionieren wird.
Morfidon
4

Können Sie einen separaten Prozess veranlassen und dann Ihre Kopie im Hintergrund ausführen? Es ist schon eine Weile her, dass ich PHP gemacht habe, aber die Funktion pcntl-fork sieht vielversprechend aus.

Matt Sheppard
quelle
Dies gibt keine Antwort auf die Frage. Um einen Autor zu kritisieren oder um Klarstellung zu bitten, hinterlassen Sie einen Kommentar unter seinem Beitrag.
Rizier123
13
Danke - Kommentare gab es in '08 nicht, aber ich werde das berücksichtigen :)
Matt Sheppard
4

Verwenden Sie diese Funktion, um Ihr Programm im Hintergrund auszuführen. Es ist plattformübergreifend und vollständig anpassbar.

<?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;
}

Beachten Sie, dass diese Funktion nach dem Start des Befehls standardmäßig das stdin und stdout des laufenden Prozesses schließt. Sie können die Prozessausgabe über die Argumente $ redirectStdout und $ redirectStderr in eine Datei umleiten.

Hinweis für Windows-Benutzer:
Sie können stdout / stderr nicht auf nulfolgende Weise umleiten :

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

Sie können dies jedoch tun:

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

Hinweise für * nix Benutzer:

1) Verwenden Sie den Befehl exec shell, wenn Sie die tatsächliche PID erhalten möchten:

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

2) Verwenden Sie das Argument $ stdin, wenn Sie einige Daten an die Eingabe Ihres Programms übergeben möchten:

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

Sie könnten ein Warteschlangensystem wie Resque ausprobieren . Sie können dann einen Job generieren, der die Informationen verarbeitet und mit dem "Verarbeitungs" -Bild recht schnell zurückkehrt. Mit diesem Ansatz wissen Sie jedoch nicht, wann es fertig ist.

Diese Lösung ist für größere Anwendungen gedacht, bei denen Ihre Frontmaschinen nicht schwer heben sollen, damit sie Benutzeranforderungen verarbeiten können. Daher kann es mit physischen Daten wie Dateien und Ordnern funktionieren oder auch nicht, aber für die Verarbeitung komplizierterer Logik oder anderer asynchroner Aufgaben (dh neuer Registrierungsmails) ist es schön und sehr skalierbar.

Scones
quelle
2

Eine funktionierende Lösung für Windows und Linux. Weitere Informationen finden Sie auf meiner Github-Seite .

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;
                        }
                    }
            }
Joshy Francis
quelle
2

Dank dieser Antwort : Ein perfektes Tool zum Ausführen eines Hintergrundprozesses wäre Symfony Process Component , das auf proc_*Funktionen basiert , aber viel einfacher zu verwenden ist. Weitere Informationen finden Sie in der Dokumentation .

MAChitgarha
quelle
1

Anstatt einen Hintergrundprozess zu initiieren, können Sie eine Triggerdatei erstellen und einen Scheduler wie cron oder autosys regelmäßig ein Skript ausführen lassen, das nach den Triggerdateien sucht und auf diese reagiert. Die Trigger können Anweisungen oder sogar Rohbefehle enthalten (noch besser, machen Sie es einfach zu einem Shell-Skript).

Brian Warshaw
quelle
1

Ich benutze stark fast_cgi_finish_request()

In Kombination mit einem Closure und register_shutdown_function ()

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

Registrieren Sie dann diesen Abschluss, der vor dem Herunterfahren ausgeführt werden soll.

register_shutdown_function($backgroundJob);

Wenn die Antwort an den Client gesendet wurde, können Sie die Verbindung zum Client schließen und mit dem PHP-Prozess weiterarbeiten:

fast_cgi_finish_request();

Der Abschluss wird nach fast_cgi_finish_request ausgeführt.

Die $ -Nachricht ist zu keinem Zeitpunkt sichtbar. Sie können so viele Abschlüsse registrieren, wie Sie möchten, aber achten Sie auf die Ausführungszeit des Skripts. Dies funktioniert nur, wenn PHP als Fast CGI-Modul ausgeführt wird (war das richtig?!)

sgotre
quelle
0

PHP-Skripte sind nicht mit anderen Entwicklungssprachen für Desktop-Anwendungen vergleichbar. In Desktop-Anwendungssprachen können wir Daemon-Threads so einstellen, dass ein Hintergrundprozess ausgeführt wird. In PHP tritt jedoch ein Prozess auf, wenn Benutzer eine Seite anfordern. Es ist jedoch möglich, einen Hintergrundjob mithilfe der Cron-Job-Funktionalität des Servers festzulegen, die vom PHP-Skript ausgeführt wird.

Shihab mm
quelle
0

Für diejenigen von uns, die Windows verwenden , sehen Sie sich Folgendes an:

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

Auch ich hatte Probleme damit, ein Programm in Windows im Hintergrund auszuführen, während das Skript weiterhin ausgeführt wird. Diese Methode ermöglicht es Ihnen im Gegensatz zu den anderen Lösungen, jedes Programm zu starten, das minimiert, maximiert oder ohne Fenster ist. Die Lösung von llbra @ phpbrasil funktioniert zwar, erzeugt jedoch manchmal ein unerwünschtes Fenster auf dem Desktop, wenn die Aufgabe wirklich versteckt ausgeführt werden soll.

start Notepad.exe im Hintergrund minimiert:

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

Starten Sie einen Shell-Befehl, der im Hintergrund unsichtbar ist:

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

Starten Sie MSPaint maximiert und warten Sie, bis Sie es schließen, bevor Sie mit dem Skript fortfahren:

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

Weitere Informationen zur Run () -Methode finden Sie unter: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Bearbeitete URL:

Gehen Sie stattdessen zu https://technet.microsoft.com/en-us/library/ee156605.aspx, da der obige Link nicht mehr vorhanden ist.

Jimmy Ilenloa
quelle
1
Was muss getan werden, um diesen Code auszuführen? Ich bekommeFatal error: Class 'COM' not found
Alph.Dev
Ps Link zu MSDN ist unterbrochen
Alph.Dev
@ Alph.Dev: Das msdn-Dokument scheint vorübergehend entfernt worden zu sein. Es enthielt weitere Beispiele und Erklärungen zur Verwendung von WScript.Shell.
Jimmy Ilenloa
@ Alph.Dev: Für die Klasse COM nicht gefunden, ist das eine ganz andere Sache. Überprüfen Sie das auf Google. Sie können auch diese php.net/manual/en/com.installation.php
Jimmy Ilenloa
@ Alph.Dev Bitte überprüfen Sie technet.microsoft.com/en-us/library/ee156605.aspx für aktualisierte Informationen über die Run () -Funktion
Jimmy Ilenloa
-12

Ich weiß, dass es sich um einen 100 Jahre alten Beitrag handelt, dachte aber trotzdem, dass er für jemanden nützlich sein könnte. Sie können irgendwo auf der Seite ein unsichtbares Bild platzieren, das auf die URL verweist, die im Hintergrund ausgeführt werden muss, wie folgt:

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

Alex
quelle
12
Wirklich schlechte Lösung. Wenn der Benutzer die Seite verlassen würde, wird der Prozess unterbrochen.
Sergey P. aka azure
3
Das "unsichtbare Bild" und sein Link werden im Seitenquellcode angezeigt.
Tony Gil