php wykonuje proces w tle

259

Muszę wykonać kopię katalogu po akcji użytkownika, ale katalogi są dość duże, więc chciałbym móc wykonać taką akcję bez uświadomienia użytkownikowi czasu potrzebnego na wykonanie kopii.

Wszelkie sugestie będą mile widziane.

tim
źródło
3
Ten stackoverflow.com/questions/5367261/… wyjaśnia, jak to zrobić w systemie Windows
shealtiel,

Odpowiedzi:

365

Zakładając, że działa na komputerze z systemem Linux, zawsze postępowałem w ten sposób:

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

Uruchamia to polecenie $cmd, przekierowuje dane wyjściowe polecenia $outputfilei zapisuje identyfikator procesu $pidfile.

To pozwala łatwo monitorować, co robi proces i czy nadal działa.

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
źródło
2
Czy konfiguracja sudo ma działać bez pytania o hasło? Wszelkie polecenia wymagające wkładu użytkownika nie będą działać.
Mark Biek
17
Ten stackoverflow.com/questions/5367261/… wyjaśnia, jak zrobić to samo w systemie Windows
Shealtiel,
13
Użyłem tego, ale zaktualizowałem go nieco, więc nie musiałem zapisywać PID do pliku. Używam więc tego formatu: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> Wynikowy proces PID jest w $ pidArr [0]
Kaiesh
3
@Kiesies: powinno być „echo $!”
brunobg
8
Kaiesh zrobił anoter literówkę „$” zamiast „%”. Poprawiona wersja: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor
23

Napisz proces jako skrypt po stronie serwera w dowolnym języku (php / bash / perl / etc), a następnie wywołaj go z funkcji kontroli procesu w skrypcie php.

Funkcja prawdopodobnie wykrywa, czy jako strumień wyjściowy zastosowano standardowe io, a jeśli tak, to ustawi wartość zwracaną .. jeśli nie, to kończy się

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

Przetestowałem to szybko z wiersza poleceń, używając polecenia „sleep 25s” jako polecenia i działało to jak urok.

( Znaleziono odpowiedź tutaj )

Eric Goodwin
źródło
3
To jedyny sposób, w jaki mogłem zmusić go do pracy na centach. Dziękuję Ci!
Deac Karns,
Dzięki ! Istnieje niezgodność PHP i BASH. Jeśli uruchomisz nowszy system, uruchomienie w tle nie powiedzie się za pomocą system () lub exec (). Deskryptory plików wydają się blokować nawet w przypadku przekierowania. Twoja metoda zadziałała. Inną alternatywą jest użycie starego binarnego basha dla PHP, ale to wariactwo
Jan
22

Możesz spróbować dołączyć to do swojego polecenia

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

na przykład.

shell_exec('service named reload >/dev/null 2>/dev/null &');
wlf
źródło
Miałem na myśli> / dev / null 2> / dev / null &
wlf
To działa dla mnie i użyję makefile, aby zrobić coś na stronie serwera, dziękuję! (np. make, make restart)
Chu-Siang Lai
Jest to o wiele prostsze niż zaakceptowana odpowiedź! (Tak długo, jak nie potrzebujesz nic bardziej złożonego, na przykład sprawdzania, czy proces nadal działa.)
Sean the Bean
18

Chciałbym tylko dodać bardzo prosty przykład testowania tej funkcji w systemie Windows:

Utwórz następujące dwa pliki i zapisz je w katalogu internetowym:

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

Zezwól IUSR na zapis w katalogu, w którym utworzono powyższe pliki

Zezwól IUSR na CZYTANIE I WYKONYWANIE C: \ Windows \ System32 \ cmd.exe

Hit foreground.php z przeglądarki internetowej

Poniższe powinny zostać wyświetlone w przeglądarce z aktualnymi znacznikami czasu i zasobem lokalnym # w tablicy wyjściowej:

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

Powinieneś zobaczyć testoutput.php w tym samym katalogu, w którym zostały zapisane powyższe pliki, i powinien być pusty

Powinieneś zobaczyć testprocesses.php w tym samym katalogu, w którym zostały zapisane powyższe pliki, i powinien zawierać następujący tekst z aktualnymi znacznikami czasu:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610
Oczywista oczywistość
źródło
10

Jeśli potrzebujesz zrobić coś w tle bez oczekiwania strony PHP na zakończenie, możesz użyć innego (w tle) skryptu PHP, który jest „wywoływany” poleceniem wget. Ten skrypt PHP w tle będzie wykonywany z uprawnieniami, oczywiście, jak każdy inny skrypt PHP w twoim systemie.

Oto przykład systemu Windows używającego wget z pakietów gnuwin32.

Kod w tle (plik test-proc-bg.php) jako przykład ...

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

Skrypt na pierwszym planie, ten wywołujący ...

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

Musisz użyć popen / pclose, aby to działało poprawnie.

Opcje wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background
Neven Boyanov
źródło
7

Oto funkcja umożliwiająca uruchomienie procesu w tle w PHP. W końcu stworzyłem taki, który faktycznie działa również w systemie Windows, po wielu odczytach i testach różnych podejść i parametrów.

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

Uwaga 1: W systemie Windows nie używaj /Bparametru jak sugerowano gdzie indziej. Zmusza proces do uruchomienia tego samego okna konsoli co startsamo polecenie, co powoduje, że proces jest przetwarzany synchronicznie. Aby uruchomić proces w osobnym wątku (asynchronicznie), nie używaj /B.

Uwaga 2: Puste podwójne cudzysłowy start ""są wymagane, jeśli polecenie jest ścieżką cytowaną. startpolecenie interpretuje pierwszy cytowany parametr jako tytuł okna.

Alph.Dev
źródło
6

Znalazłem nieco szybszą i łatwiejszą wersję do użycia

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

i to działa.

Morfidon
źródło
@ Alpha.Dev Testowałem to na Linuksie, ale nie wiem, czy zadziała na innym systemie.
Morfidon
4

Czy potrafisz zorganizować oddzielny proces, a następnie uruchomić kopię w tle? Minęło trochę czasu odkąd zrobiłem PHP, ale funkcja pcntl-fork wygląda obiecująco.

Matt Sheppard
źródło
To nie daje odpowiedzi na pytanie. Aby skrytykować lub poprosić autora o wyjaśnienia, zostaw komentarz pod postem.
Rizier123
13
Dzięki - Komentarze nie istniały w '08, ale będę o tym pamiętać :)
Matt Sheppard
4

Użyj tej funkcji, aby uruchomić program w tle. Jest wieloplatformowy iw pełni konfigurowalny.

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

Zauważ, że po uruchomieniu polecenia ta funkcja domyślnie zamyka standardowe wejście i standardowe wyjście uruchomionego procesu. Możesz przekierować dane wyjściowe do jakiegoś pliku za pomocą argumentów $ redirectStdout i $ redirectStderr.

Uwaga dla użytkowników systemu Windows:
Nie można przekierować stdout / stderr nulw następujący sposób:

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

Możesz to jednak zrobić:

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

Uwagi dla użytkowników * nix:

1) Użyj polecenia powłoki exec, jeśli chcesz uzyskać rzeczywisty PID:

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

2) Użyj argumentu $ stdin, jeśli chcesz przekazać niektóre dane do danych wejściowych programu:

startBackgroundProcess('cat > input.txt', "Hello world!\n");
Pascal9x
źródło
3

Możesz wypróbować system kolejkowania, taki jak Resque . Następnie możesz wygenerować zadanie, które przetwarza informacje i dość szybko powraca z obrazem „przetwarzającym”. Dzięki takiemu podejściu nie będziesz wiedział, kiedy zostanie zakończony.

To rozwiązanie jest przeznaczone do zastosowań na większą skalę, gdzie nie chcesz, aby twoje maszyny czołowe wykonywały ciężkie podnoszenie, aby mogły przetwarzać żądania użytkowników. Dlatego może, ale nie musi, pracować z danymi fizycznymi, takimi jak pliki i foldery, ale do przetwarzania bardziej skomplikowanej logiki lub innych zadań asynchronicznych (tj. Nowych wiadomości rejestracyjnych) dobrze jest mieć i jest bardzo skalowalny.

bułeczki
źródło
2

Działające rozwiązanie zarówno dla systemu Windows, jak i Linux. Znajdź więcej na stronie Mój 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;
                        }
                    }
            }
Joshy Francis
źródło
1

Zamiast inicjować proces w tle, co z tworzeniem pliku wyzwalającego i planowaniem, takim jak cron lub autosys, co jakiś czas wykonuje skrypt, który wyszukuje i działa na pliki wyzwalające? Wyzwalacze mogą zawierać instrukcje lub nawet surowe polecenia (jeszcze lepiej, po prostu uczyń go skryptem powłoki).

Brian Warshaw
źródło
1

Mocno używam fast_cgi_finish_request()

W połączeniu z zamknięciem i register_shutdown_function ()

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

Następnie zarejestruj to zamknięcie, które ma zostać wykonane przed wyłączeniem.

register_shutdown_function($backgroundJob);

Wreszcie, gdy odpowiedź została wysłana do klienta, możesz zamknąć połączenie z klientem i kontynuować pracę z procesem PHP:

fast_cgi_finish_request();

Zamknięcie zostanie wykonane po fast_cgi_finish_request.

Wiadomość $ nie będzie widoczna w żadnym momencie. Możesz zarejestrować tyle zamknięć, ile chcesz, ale zadbaj o czas wykonania skryptu. Działa to tylko wtedy, gdy PHP działa jako moduł Fast CGI (czy to prawda ?!)

sgotre
źródło
0

Skrypty PHP nie są podobne do innych aplikacji rozwijających język. W językach aplikacji komputerowych możemy ustawić wątki demonów, aby uruchamiały proces w tle, ale w PHP proces występuje, gdy użytkownik żąda strony. Możliwe jest jednak ustawienie zadania w tle za pomocą funkcji zadania cron serwera, którą uruchamia skrypt php.

shihab mm
źródło
0

Dla tych z nas korzystających z systemu Windows spójrz na to:

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

Ja też zmagałem się z uruchomieniem programu w tle w systemie Windows, podczas gdy skrypt nadal działa. Ta metoda, w przeciwieństwie do innych rozwiązań, pozwala uruchomić dowolny program zminimalizowany, zmaksymalizowany lub bez okna. Rozwiązanie llbra @ phpbrasil działa, ale czasami tworzy niechciane okno na pulpicie, gdy naprawdę chcesz, aby zadanie działało ukryte.

uruchom zminimalizowany Notepad.exe w tle:

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

uruchom polecenie powłoki niewidoczne w tle:

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

uruchom MSPaint zmaksymalizowany i poczekaj na zamknięcie go przed kontynuowaniem skryptu:

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

Więcej informacji na temat metody Run () znajduje się na stronie: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Edytowany adres URL:

Przejdź do https://technet.microsoft.com/en-us/library/ee156605.aspx zamiast tego, ponieważ powyższy link już nie istnieje.

Jimmy Ilenloa
źródło
1
Co należy zrobić, aby uruchomić ten kod? DostajęFatal error: Class 'COM' not found
Alph.Dev
Ps Link do MSDN jest zepsuty
Alph.Dev
@ Alph.Dev: Wygląda na to, że dokument msdn został tymczasowo usunięty. Zawierał więcej przykładów i wyjaśnień dotyczących korzystania z WScript.Shell.
Jimmy Ilenloa
@ Alph.Dev: Dla klasy COM nie znaleziono, to zupełnie inna sprawa. Sprawdź to w Google. Możesz także sprawdzić to php.net/manual/en/com.installation.php
Jimmy Ilenloa
@ Alph.Dev proszę sprawdzić na stronie technet.microsoft.com/en-us/library/ee156605.aspx, aby uzyskać aktualne informacje na temat funkcji Run ()
Jimmy Ilenloa
-12

Wiem, że to post ma 100 lat, ale w każdym razie pomyślałem, że może się komuś przydać. Możesz umieścić niewidoczny obraz gdzieś na stronie wskazujący adres URL, który musi być uruchomiony w tle, w następujący sposób:

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

Alex
źródło
12
Naprawdę złe rozwiązanie. Jeśli użytkownik opuści stronę, proces zostanie przerwany.
Sergey P. aka lazur
3
„niewidoczny obraz” i jego link są widoczne w kodzie źródłowym strony.
Tony Gil