PHP выполнить фоновый процесс


259

Мне нужно выполнить копию каталога при действии пользователя, но каталоги довольно велики, поэтому я хотел бы иметь возможность выполнять такое действие без уведомления пользователя о времени, которое требуется для завершения копирования.

Любые предложения будут высоко ценится.


3
Это stackoverflow.com/questions/5367261/… объясняет, как сделать это под Windows
Shealtiel

Ответы:


365

Предполагая, что это работает на машине Linux, я всегда обрабатывал это так:

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

Это запускает команду $cmd, перенаправляет вывод команды на$outputfile и записывает идентификатор процесса $pidfile.

Это позволяет вам легко отслеживать, что делает процесс и работает ли он до сих пор.

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
Можно ли запустить sudo без запроса пароля? Любые команды, требующие пользовательского ввода, работать не будут.
Марк Бик

17
Это stackoverflow.com/questions/5367261/… объясняет, как сделать то же самое под окнами
shealtiel

13
Я использовал это, но немного обновил, чтобы мне не пришлось записывать PID в файл. Поэтому я использую этот формат: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </ code> Полученный PID процесса находится в $ pidArr [0]
Kaiesh

3
@Kaiesh: это должно быть "echo $!"
brunobg

8
Кайеш сделал опечатку «$» вместо «%». Исправленная версия: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Юрий Гор

23

Напишите процесс как серверный скрипт на любом удобном для вас языке (php / bash / perl / etc) и затем вызовите его из функций управления процессом в вашем php-скрипте.

Функция, вероятно, обнаруживает, используется ли стандартный поток ввода-вывода в качестве выходного потока, и если это так, тогда будет установлено возвращаемое значение.

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

Я быстро проверил это из командной строки, используя «sleep 25s» в качестве команды, и это сработало как шарм.

( Ответ найден здесь )


3
Это единственный способ заставить его работать на Centos. Спасибо!
Дик Карнс

Спасибо ! Существует несовместимость PHP и BASH. Если вы запустите более новую систему, она не сможет запуститься в фоновом режиме, используя system () или exec (). Дескрипторы файлов, похоже, застряли, даже если вы перенаправили. Ваш метод сработал. другая альтернатива - использовать старый двоичный файл bash для PHP, но это ненормально
Джон

22

Вы можете попытаться добавить это к вашей команде

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

например.

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

Я имел в виду> / dev / null 2> / dev / null &
wlf

Это работает для меня, и я буду использовать make-файл, что-то делать на сайте сервера, спасибо! (напр. make, сделать перезагрузку)
Чу-Сян Лай

Это намного проще, чем принятый ответ! (Пока вам не нужно ничего более сложного, например, проверить, запущен ли еще этот процесс.)
Шон Бин,

18

Я просто хотел бы добавить очень простой пример для тестирования этой функциональности в Windows:

Создайте следующие два файла и сохраните их в веб-каталоге:

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

Дайте IUSR разрешение на запись в каталог, в котором вы создали вышеуказанные файлы

Разрешите IUSR ПРОЧИТАТЬ и ВЫПОЛНИТЬ C: \ Windows \ System32 \ cmd.exe

Хит foreground.php из веб-браузера

Следующее должно быть отображено в браузере с текущими временными метками и локальным ресурсом # в выходном массиве:

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

Вы должны увидеть testoutput.php в том же каталоге, в котором были сохранены вышеуказанные файлы, и он должен быть пустым

Вы должны увидеть testprocesses.php в том же каталоге, в котором были сохранены вышеуказанные файлы, и он должен содержать следующий текст с текущими временными метками:

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

10

Если вам нужно просто что-то сделать в фоновом режиме, пока страница PHP не ожидает его завершения, вы можете использовать другой (фоновый) скрипт PHP, который «вызывается» с помощью команды wget. Этот фоновый PHP-скрипт будет выполняться с привилегиями, как и любой другой PHP-скрипт в вашей системе.

Вот пример для Windows, использующий wget из пакетов gnuwin32.

Фоновый код (файл test-proc-bg.php) в качестве примера ...

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

Скрипт переднего плана, вызывающий ...

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

Вы должны использовать popen / pclose для правильной работы.

Варианты wget:

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

7

Вот функция для запуска фонового процесса в PHP. Наконец-то создали тот, который на самом деле работает и в Windows, после большого чтения и тестирования различных подходов и параметров.

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

Примечание 1: В окнах не используйте /Bпараметр, как предложено в другом месте. Это заставляет процесс запускать то же самое окно консоли, что и startсама команда, в результате чего процесс обрабатывается синхронно. Для запуска процесса в отдельном потоке (асинхронно) не используйте /B.

Примечание 2: Пустые двойные кавычки после start ""обязательны, если команда является путем в кавычках. startКоманда интерпретирует первый цитируемый параметр как заголовок окна.


6

Ну, я нашел немного быстрее и проще в использовании версию

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

и это работает.


@ Alpha.Dev Я тестировал его только на Linux, я не знаю, сработает ли он на другой системе.
Морфидон

4

Можете ли вы организовать отдельный процесс, а затем запустить свою копию в фоновом режиме? Я давно не занимался PHP, но функция pcntl-fork выглядит многообещающе.


Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом.
Rizier123

13
Спасибо - Комментариев не было в '08, но я буду иметь это в виду :)
Мэтт Шеппард

4

Используйте эту функцию для запуска вашей программы в фоновом режиме. Он кроссплатформенный и полностью настраиваемый.

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

Обратите внимание, что после запуска команды по умолчанию эта функция закрывает stdin и stdout запущенного процесса. Вы можете перенаправить вывод процесса в некоторый файл с помощью аргументов $ redirectStdout и $ redirectStderr.

Примечание для пользователей Windows:
Вы не можете перенаправить stdout / stderr nulследующим образом:

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

Тем не менее, вы можете сделать это:

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

Примечания для пользователей * nix:

1) Используйте команду оболочки exec, если вы хотите получить фактический PID:

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

2) Используйте аргумент $ stdin, если вы хотите передать некоторые данные на вход вашей программы:

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

3

Вы можете попробовать систему очередей, как Resque . Затем вы можете создать задание, которое обрабатывает информацию и довольно быстро возвращается с «обработкой» изображения. При таком подходе вы не будете знать, когда он закончится.

Это решение предназначено для более масштабных приложений, когда вы не хотите, чтобы ваши передние машины выполняли тяжелую работу, чтобы они могли обрабатывать запросы пользователей. Следовательно, он может работать или не работать с физическими данными, такими как файлы и папки, но для обработки более сложной логики или других асинхронных задач (например, новых почтовых сообщений регистрации) его приятно иметь и очень масштабируемый.


2

Рабочее решение для Windows и Linux. Узнайте больше на странице My 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

Вместо того, чтобы инициировать фоновый процесс, как насчет создания файла триггера и того, чтобы планировщик типа cron или autosys периодически выполнял скрипт, который ищет и воздействует на файлы триггера? Триггеры могут содержать инструкции или даже необработанные команды (еще лучше, просто сделайте это сценарием оболочки).



1

Я сильно использую fast_cgi_finish_request()

В сочетании с замыканием и register_shutdown_function ()

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

Затем зарегистрируйте это закрытие, которое будет выполнено перед выключением.

register_shutdown_function($backgroundJob);

Наконец, когда ответ был отправлен клиенту, вы можете закрыть соединение с клиентом и продолжить работу с процессом PHP:

fast_cgi_finish_request();

Закрытие будет выполнено после fast_cgi_finish_request.

Сообщение $ не будет видно в любое время. И вы можете зарегистрировать столько замыканий, сколько захотите, но позаботьтесь о времени выполнения скрипта. Это будет работать, только если PHP работает как модуль Fast CGI (это было так ?!)


0

Сценарии PHP не похожи на другие языки разработки приложений для настольных компьютеров. В языках настольных приложений мы можем настроить потоки демона для запуска фонового процесса, но в PHP процесс происходит, когда пользователь запрашивает страницу. Однако можно установить фоновое задание, используя функции сервера cron, которые запускает скрипт php.


0

Для тех из нас, кто использует Windows , посмотрите на это:

Ссылка: http://php.net/manual/en/function.exec.php#43917

Я тоже боролся с тем, чтобы заставить программу работать в фоновом режиме в Windows, пока скрипт продолжает выполняться. Этот метод, в отличие от других решений, позволяет запускать любую программу в свернутом, развернутом виде или без окна вообще. Решение llbra @ phpbrasil работает, но иногда оно создает нежелательное окно на рабочем столе, когда вы действительно хотите, чтобы задача выполнялась скрытой.

запуск Notepad.exe сворачивается в фоновом режиме:

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

запустить команду оболочки, невидимую в фоновом режиме:

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

Запустите MSPaint в развернутом виде и подождите, пока вы закроете его, прежде чем продолжить скрипт:

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

Для получения дополнительной информации о методе Run () перейдите по адресу : http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Отредактированный URL:

Вместо этого перейдите по ссылке https://technet.microsoft.com/en-us/library/ee156605.aspx, поскольку вышеуказанная ссылка больше не существует.


1
Что нужно сделать, чтобы запустить этот код? Я получаюFatal error: Class 'COM' not found
Alph.Dev

Ps ссылка на MSDN не работает
Alph.Dev

@ Alph.Dev: документ MSDN, кажется, был временно удален. Он содержал больше примеров и объяснений того, как использовать WScript.Shell.
Джимми Иленлоа

@ Alph.Dev: Для класса COM не найден, это совсем другое дело. Проверьте это на Google. Вы также можете проверить этот php.net/manual/en/com.installation.php
Джимми Иленлоа

@ Alph.Dev, пожалуйста, проверьте technet.microsoft.com/en-us/library/ee156605.aspx для получения обновленной информации о функции Run ()
Джимми Иленлоа,

-12

Я знаю, что это 100-летний пост, но все равно подумал, что это может быть кому-то полезно. Вы можете поместить невидимое изображение где-нибудь на странице, указывая на URL, который должен работать в фоновом режиме, например так:

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


12
Действительно плохое решение. Если пользователь покинет страницу, процесс будет прерван.
Сергей П. ака лазурный

3
«Невидимое изображение» и его ссылка отображаются в исходном коде страницы.
Тони Гил
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.