75

我正在尝试在网页上运行一个进程,该进程将实时返回其输出。例如,如果我运行“ping”进程,它应该在每次返回新行时更新我的​​页面(现在,当我使用 exec(command, output) 时,我被迫使用 -c 选项并等到进程完成才能看到在我的网页上输出)。是否可以在 php 中执行此操作?

我也想知道当有人离开页面时终止这种进程的正确方法是什么。在“ping”进程的情况下,我仍然能够看到系统监视器中运行的进程(这是有道理的)。

4

11 回答 11

101

这对我有用:

$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>";
于 2011-05-26T19:48:57.940 回答
37

这是显示 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);
}

我希望这可以帮助其他在这里用谷歌搜索的人。

于 2011-05-10T21:30:48.783 回答
13

检查了所有答案,没有任何效果...

在这里找到解决方案

它适用于 Windows(我认为这个答案对在那里搜索的用户很有帮助)

<?php
    $a = popen('ping www.google.com', 'r'); 
    
    while($b = fgets($a, 2048)) { 
        echo $b."<br>\n"; 
        ob_flush();flush(); 
    }

    pclose($a); 
?>
于 2017-09-07T16:37:51.450 回答
7

此处描述了使用现代 HTML5 服务器端事件更好地解决这个老问题的方法:

http://www.w3schools.com/html/html5_serversentevents.asp


例子:

http://sink.agiletoolkit.org/realtime/console

代码:https ://github.com/atk4/sink/blob/master/admin/page/realtime/console.php#L40

(在 Agile Toolkit 框架中作为模块实现)

于 2015-01-30T20:55:07.480 回答
6

对于命令行用法:

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);
于 2014-04-01T15:31:30.207 回答
5

试试这个(在 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();
于 2013-07-15T16:08:35.200 回答
5

我在 Windows 上尝试了各种 PHP 执行命令,发现它们差别很大。

  • 不适用于流式传输:shell_exec, exec,passthru
  • 作品种类:proc_open, popen-- “种类”,因为您不能将参数传递给您的命令(即不会与 一起使用my.exe --something,将与 一起使用_my_something.bat)。

最好(最简单)的方法是:

  1. 您必须确保您的 exe 正在刷新命令(请参阅printf 刷新问题)。没有这个,你很可能会收到大约 4096 字节的文本,无论你做什么。
  2. 如果可以,请使用header('Content-Type: text/event-stream');(而不是header('Content-Type: text/plain; charset=...');)。不过,这不适用于所有浏览器/客户端!没有这个,流式传输将起作用,但至少第一行将被浏览器缓冲。
  3. 您可能还想禁用缓存header('Cache-Control: no-cache');
  4. 关闭输出缓冲(在 php.ini 或 with 中ini_set('output_buffering', 'off');)。这可能还必须在 Apache/Nginx/您前面使用的任何服务器中完成。
  5. 打开压缩(在 php.ini 或 with 中ini_set('zlib.output_compression', false);)。这可能还必须在 Apache/Nginx/您前面使用的任何服务器中完成。

因此,在您的 C++ 程序中,您可以执行以下操作(同样,对于其他解决方案,请参阅printf 冲洗问题):

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);
于 2015-07-29T08:34:27.717 回答
3

如果您希望通过 PHP 运行系统命令,请查看exec 文档

不过,我不建议在高流量站点上执行此操作,为每个请求分叉一个进程是一个相当大的过程。一些程序提供将其进程 ID 写入文件的选项,以便您可以检查并随意终止进程,但对于 ping 等命令,我不确定是否可行,请查看手册页。

只需在远程主机上您希望侦听的端口(IE:HTTP 的端口 80)上打开一个套接字,您可能会得到更好的服务,这样您就知道用户空间和网络上的一切进展顺利。

如果您尝试输出二进制数据,请查看php 的 header 函数,并确保设置正确的content-typecontent-disposition。查看文档,了解有关使用/禁用输出缓冲区的更多信息。

于 2009-08-15T05:11:14.597 回答
3

首先检查flush()是否适合您。如果有,很好,如果没有,则可能意味着 Web 服务器由于某种原因正在缓冲,例如启用了 mod_gzip。

对于 ping 之类的东西,最简单的技术是在 PHP 中循环,多次运行“ping -c 1”,并在每次输出后调用 flush()。假设 PHP 配置为在用户关闭 HTTP 连接时中止(这通常是默认设置,或者您可以调用 ignore_user_abort(false) 来确保),那么您也不必担心失控的 ping 进程.

如果确实有必要只运行一次子进程并连续显示其输出,那可能会更困难——您可能必须在后台运行它,将输出重定向到流,然后让 PHP 回显该流返回给用户,穿插常规的 flush() 调用。

于 2009-08-15T05:14:40.797 回答
1

尝试更改 php.ini 文件集“output_buffering = Off”。您应该能够在页面上获得实时输出使用系统命令而不是 exec.. 系统命令将刷新输出

于 2009-09-15T20:38:14.093 回答
0

为什么不将输出通过管道传输到日志文件中,然后使用该文件将内容返回给客户端。不是很实时,但也许足够好?

于 2018-08-09T15:28:40.243 回答