Я пытаюсь запустить процесс на веб-странице, который будет возвращать свой вывод в реальном времени. Например, если я запускаю процесс 'ping', он должен обновлять мою страницу каждый раз, когда возвращается результат операции (сейчас, когда я использую exec(command, output), я вынужден использовать опцию -c и ждать окончания процесса, чтобы увидеть вывод на моей веб-странице). Возможно ли это сделать в php? Мне также интересно, как правильно завершить такой процесс, когда кто-то покидает страницу. В случае с процессом 'ping' я все еще могу видеть запущенный процесс в системном мониторе (что не имеет смысла).
Ответ 1
Это сработало для меня:
$cmd = "ping 127.0.0.1";
$descriptorspec = array(
0 => array("pipe", "r"), // stdin – это пайп, из которого дочерний процесс будет читать
1 => array("pipe", "w"), // stdout – этот пайп, в который дочерний процесс будет писать
2 => array("pipe", "w") // stderr – это пайп в который дочерний процесс будет выводить ошибки (если будут)
);
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>";
Ответ 2
Вот хороший способ показать вывод команд оболочки в реальном времени:
<?php
header("Content-type: text/plain");
// сообщите php, чтобы он автоматически фильтровал данные после каждого вывода.
// включая строки вывода, производимые командами shell
disable_ob();
$command = 'rsync -avz /your/directory1 /your/directory2';
system($command);
Эта функция понадобится вам для предотвращения буферизации вывода:
function disable_ob() {
// Выключить буферизацию вывода
ini_set('output_buffering', 'off');
// Отключить сжатие вывода PHP
ini_set('zlib.output_compression', false);
// Неявная очистка буфера(ов)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Очистить и выключить буферизацию вывода
while (ob_get_level() > 0) {
// Получить текущий уровень
$level = ob_get_level();
// Завершение буферизации
ob_end_clean();
// Если текущий уровень не изменился, прервать работу
if (ob_get_level() == $level) break;
}
// Отключить буферизацию/компрессию вывода apache
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
}
Однако этот код не работает на всех серверах, на которых я его пробовал. Вот фиктивный пример на обычном PHP:
<?php
header("Content-type: text/plain");
disable_ob();
for($i=0;$i<10;$i++) {
echo $i . "\n";
usleep(300000);
}
Ответ 3
Для использования в командной строке:
function execute($cmd) {
$proc = proc_open($cmd, , $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);
}
Если вы пытаетесь запустить файл, вам может потребоваться сначала предоставить ему разрешения на выполнение:
chmod('/path/to/script',0755);
Ответ 4
Попробуйте следующее (проверено на машине Windows + сервер 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();
Ответ 5
Я пробовал различные команды выполнения PHP под Windows и обнаружил, что они довольно сильно различаются:
-
Не работают для потоковой передачи: shell_exec, exec, passthru.
-
Вроде как работают: proc_open, popen -- «вроде как», потому что вы не можете передать аргументы команде (т. е. не будет работать с my.exe --something, будет работать с _my_something.bat).
Лучший (самый простой) подход заключается в следующем:
-
Вы должны убедиться, что ваш exe выполняет фильтрацию команд (см. проблему фильтрации printf). Без этого вы, скорее всего, будете получать порции текста размером около 4096 байт, что бы вы ни делали.
-
Если можете, используйте header('Content-Type: text/event-stream'); (вместо header('Content-Type: text/plain; charset=...');). Однако это будет работать не во всех браузерах/клиентах! Потоковая передача будет работать и без этого, но, по крайней мере, первые строки будут буферизироваться браузером.
-
Вы также можете отключить кэширование header('Cache-Control: no-cache');.
-
Отключите буферизацию вывода (либо в php.ini, либо с помощью ini_set('output_buffering', 'off');). Это также может быть необходимо сделать в Apache/Nginx/каком-либо другом сервере, который вы используете.
-
Отключение сжатия (либо в php.ini, либо с помощью ini_set('zlib.output_compression', false);). Это также может быть необходимо сделать в Apache/Nginx/в любом другом сервере, который вы используете.
Итак, в вашей программе на C++ вы делаете что-то вроде этого (опять же, для других решений смотрите printf flushing problem):
Logger::log(...) {
printf (text);
fflush(stdout);
}
В PHP же необходимо сделать что-то вроде этого:
function setupStreaming() {
// Выключить буферизацию вывода
ini_set('output_buffering', 'off');
// Отключить сжатие вывода PHP
ini_set('zlib.output_compression', false);
// Отключить буферизацию/компрессию вывода Apache
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);
Ответ 6
Сначала проверьте, работает ли у вас flush(). Если да — хорошо, если нет, то, скорее всего, веб-сервер по какой-то причине буферизирует вывод, например, включен mod_gzip.
Для чего-то вроде ping самая простая техника — зациклиться в PHP, выполняя «ping -c 1» несколько раз и вызывая flush() после каждого вывода. Если PHP настроен на прерывание при закрытии HTTP-соединения пользователем (что обычно происходит по умолчанию, или вы можете вызвать ignore_user_abort(false), чтобы убедиться в этом), то вам не нужно беспокоиться о запущенных процессах ping. Если вам действительно необходимо запустить дочерний процесс только один раз и отображать его вывод постоянно, то это может быть сложнее — вероятно, вам придется запускать его в фоновом режиме, перенаправлять вывод в поток, а затем заставлять PHP эхом возвращать этот поток пользователю, перемежая его регулярными вызовами flush().
Ответ 7
Если вы хотите выполнять системные команды через PHP, посмотрите документацию по exec.
Однако я бы не рекомендовал делать это на сайте с высокой посещаемостью, поскольку форк процесса для каждого запроса — это довольно тяжелый процесс. Некоторые программы предоставляют возможность записывать идентификатор процесса в файл, чтобы вы могли проверить и завершить процесс по желанию, но для таких команд, как ping, я не уверен, что это возможно. Проверьте страницы руководства.
Возможно, вам лучше просто открыть сокет на порту, который вы ожидаете прослушивать (IE: порт 80 для HTTP) на удаленном хосте; таким образом, вы будете знать, что все идет хорошо как в пользовательской среде, так и в сети. Если вы пытаетесь вывести двоичные данные, посмотрите на функцию заголовков php и убедитесь, что вы установили правильный content-type и content-disposition. Просмотрите документацию для получения дополнительной информации об использовании/отключении буфера вывода.