我正在尝试在网页上运行一个进程,并实时返回其输出。例如,如果我运行'ping'进程,它应该每次返回一个新的行时更新我的页面(目前,当我使用exec(command, output)时,我被迫使用-c选项并等待进程完成才能在我的网页上看到输出)。这在php中是否可能实现?
我还想知道当有人离开页面时,正确的杀死此类进程的方法是什么。对于'ping'进程,我仍然能够在系统监视器中看到进程正在运行(这是有意义的)。
这对我有用:
$cmd = "ping 127.0.0.1";
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
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>";
这是一个不错的方法,可以显示您的 shell 命令的实时输出:
<?php
header("Content-type: text/plain");
// tell php to automatically flush after every output
// including lines of output produced by shell commands
disable_ob();
$command = 'rsync -avz /your/directory1 /your/directory2';
system($command);
你需要这个函数来防止输出缓冲:function disable_ob() {
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
}
// Disable apache output buffering/compression
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
}
不过,它在我尝试的每台服务器上都不起作用,我希望能够提供一些关于在您的php配置中查找什么来确定是否应该在服务器上尝试这种类型行为时绝望地抓狂的建议!还有其他人知道吗?
以下是一个简单的PHP示例:
<?php
header("Content-type: text/plain");
disable_ob();
for($i=0;$i<10;$i++)
{
echo $i . "\n";
usleep(300000);
}
我希望这篇内容能帮助那些通过谷歌搜索到这里的人。
exec()
。但是 system()
可以正常工作。 - divinedragonfor ($i=0; $i < 100000; $i++) { echo " "; } echo "\n"; flush();
- Martin Pecka检查了所有答案,都不起作用...
在这里找到了解决方案。
它适用于Windows(我认为这个答案对在那里搜索的用户有帮助)
<?php
$a = popen('ping www.google.com', 'r');
while($b = fgets($a, 2048)) {
echo $b."<br>\n";
ob_flush();flush();
}
pclose($a);
?>
对于命令行使用:
function execute($cmd) {
$proc = proc_open($cmd, [['pipe','r'],['pipe','w'],['pipe','w']], $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);
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();
shell_exec
、exec
和 passthru
:不支持流式传输。proc_open
和 popen
:“有点”可行,因为无法向命令传递参数(例如 my.exe --something
无法运行,但可以使用 _my_something.bat
)。header('Content-Type: text/event-stream');
(而非 header('Content-Type: text/plain; charset=...');
)。然而,这并不能在所有浏览器/客户端上实现!即使没有此设置,流式传输仍将起作用,不过第一行将被浏览器缓冲。header('Cache-Control: no-cache');
。ini_set('output_buffering', 'off');
)。这也可能需要在Apache/Nginx/等用于前端的服务器中进行设置。ini_set('zlib.output_compression', false);
)。这也可能需要在Apache/Nginx/等用于前端的服务器中进行设置。Logger::log(...) {
printf (text);
fflush(stdout);
}
在PHP中,您可以执行以下操作:
function setupStreaming() {
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Disable Apache output buffering/compression
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);
首先检查一下flush()是否适合您的情况。如果可以,那就太好了。如果不行,那可能是因为Web服务器出于某种原因进行了缓冲,例如启用了mod_gzip。
对于诸如ping之类的东西,最简单的技术是在PHP内循环运行"ping -c 1"多次,并在每次输出后调用flush()。假设PHP配置为在用户关闭HTTP连接时中止(这通常是默认设置,或者您可以调用ignore_user_abort(false)确保),那么您也不必担心跑偏的ping进程。
如果真的有必要只运行一次子进程并连续显示其输出,则可能更困难--您可能需要在后台运行它,将输出重定向到流中,然后让PHP将该流回显给用户,并穿插使用常规的flush()调用。
我曾经遇到过同样的问题,但只能使用Symfony进程组件(https://symfony.com/doc/current/components/process.html)来解决。
以下是一个快速示例:
<?php
use Symfony\Component\Process\Process;
$process = new Process(['ls', '-lsa']);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
?>
header('X-Accel-Buffering: no');
- Nylls