代码之家  ›  专栏  ›  技术社区  ›  Maksim Vi.

在PHP中运行带有实时输出的进程

  •  62
  • Maksim Vi.  · 技术社区  · 15 年前

    我尝试在网页上运行一个进程,该进程将实时返回其输出。例如,如果我运行“ping”进程,它应该在每次返回新行时更新我的页面(现在,当我使用exec(命令、输出)时,我必须使用-c选项,并等待进程完成,以查看我的网页上的输出)。在PHP中可以这样做吗?

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

    11 回复  |  直到 7 年前
        1
  •  85
  •   Petah    12 年前

    这对我很有用:

    $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>";
    
        2
  •  35
  •   Petah    12 年前

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

    我希望这能帮助那些在谷歌上搜索过的人。

        3
  •  6
  •   romaninsh    9 年前

    下面介绍了使用现代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

    (在敏捷工具包框架中作为模块实现)

        4
  •  4
  •   raminious    12 年前

    尝试此操作(在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
  •  4
  •   Pixsa    7 年前

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

    找到解决方案 Here

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

    <?php
    $a = popen('ping www.google.com', 'r'); 
    
    while($b = fgets($a, 2048)) { 
    echo $b."<br>\n"; 
    ob_flush();flush(); 
    } 
    pclose($a); 
    
    ?>
    
        6
  •  3
  •   Community CDub    8 年前

    我在Windows上尝试过各种PHP执行命令,结果发现它们有很大的不同。

    • 不适用于流媒体: shell_exec , exec , passthru
    • 工程类型: proc_open , popen --“有点”,因为您不能将参数传递给命令(即不使用 my.exe --something ,将与 _my_something.bat )

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

    1. 必须确保您的exe正在刷新命令(请参见 printf flushing problem )。如果没有这个,你很可能会收到大约4096字节的文本批,无论你做什么。
    2. 如果可以,请使用 header('Content-Type: text/event-stream'); 而不是 header('Content-Type: text/plain; charset=...'); )但是,这在所有浏览器/客户机中都不起作用!如果没有这个,流媒体就可以工作,但浏览器至少会缓冲第一行。
    3. 您可能还想禁用缓存 header('Cache-Control: no-cache'); .
    4. 关闭输出缓冲(在php.ini或 ini_set('output_buffering', 'off'); )这可能还必须在Apache/nginx/前面使用的任何服务器中完成。
    5. 压缩转换(php.ini或 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);
    
        7
  •  2
  •   Todd Owen    15 年前

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

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

    如果真的有必要只运行子进程 一旦 并且不断地显示它的输出,这可能会更困难——您可能需要在后台运行它,将输出重定向到流,然后让PHP echo返回到用户,并散布常规的flush()调用。

        8
  •  2
  •   Nathacof    15 年前

    如果要通过php运行系统命令,请查看 the exec documentation .

    不过,我不建议在流量大的站点上这样做,为每个请求分配一个进程是一个相当复杂的过程。有些程序提供了将进程ID写入文件的选项,这样您就可以随意检查和终止进程,但是对于诸如ping之类的命令,我不确定是否可能,请检查手册页。

    只需在远程主机上打开您希望监听的端口(即,端口80用于HTTP)上的套接字,这样您就可以更好地了解用户区以及网络上的一切情况。

    如果您试图输出二进制数据,请查看 php's header function 并确保设置正确的 content-type content-disposition . 回顾 the documentation 有关使用/禁用输出缓冲区的详细信息。

        9
  •  2
  •   mpen    11 年前

    对于命令行用法:

    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);
    
        10
  •  0
  •   Yatin    15 年前

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

        11
  •  0
  •   greenone83    7 年前

    为什么不直接将输出导入日志文件,然后使用该文件将内容返回到客户机。不是很实时,但也许足够好?