在流中读取实时整个输出

Read realtime whole output in stream

注意:这个 post 与这个 post 不同,后者接受的答案只是一次阅读每一行。

我必须在服务器端切割 3D 模型进行 3D 打印,这个过程会花费一些时间。所以我要给用户展示流程,我用redis来存储流程。我想每 0.5 秒刷新一次进程。 比如sleep 0.5sec,每次读取pip中的所有内容并处理。

目前我已经尝试了以下两个,第一个将一直保持到完成。第二种使用while不是一个正确的方式,它会一直写redis会导致client read process request hold到最后。

我试过这两个:

第一个将保持到命令完成。

$descriptorspec = array(
    0 => array("pipe", "r"),
    1 => array("pipe", "w"),
    2 => array("pipe", "w")    //here curaengine log all the info into stderror
);
$command = './CuraEngine slice -p -j ' . $fdmprinterpath . ' -j ' . $configpath . ' -o ' . $gcodepath . ' -l ' . $tempstlpath;
$cwd = '/usr/local/curaengine';
$process = proc_open($command, $descriptorspec, $pipes, $cwd);
if(is_resource($process))
{
    print stream_get_contents($pipes[1]);  //This will hold until the command finished.
}

和第二个实施为 post 将每次一行。

  $descriptorspec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")    //here curaengine log all the info into stderror
    );
    $command = './CuraEngine slice -p -j ' . $fdmprinterpath . ' -j ' . $configpath . ' -o ' . $gcodepath . ' -l ' . $tempstlpath;
    $cwd = '/usr/local/curaengine';
    $process = proc_open($command, $descriptorspec, $pipes, $cwd);
    if(is_resource($process))
    {
        while ($s = fgets($pipes[1])) {
            print $s;
            flush();
        }
    }

fread()代替fgets().

看看Symfony component Process。它提供简单的异步流程和实时流程输出等。例如:

$process = new Process($command);
$process->start('processOutput');

while ($process->isRunning()) {
    usleep(500000);
    $progress = $process->getIncrementalOutput();
    progressOutput($progress);
}

$output = $process->getOutput();

//
// Example implementations:
//
function progressOutput($progress) {        
    echo $progress;
    ob_flush();
}

function processOutput($type, $output) {
    if ($type == Process::OUT) {
        echo $output;
        ob_flush();
    }
}

您应该首先通过将此行添加到代码的开头来禁用输出缓冲:

while(@ob_end_clean());

添加该行后,您的第二个代码应该可以流式传输了。

不幸的是,即使禁用了输出缓冲,输出流的结果也取决于浏览器。 我测试了几种浏览器,包括桌面和移动设备, 并发现输出流在基于 Chromium 的浏览器中有效,但在 Firefox 中无效。

因为它依赖于浏览器,所以您使用的功能,无论是 fgets() 还是 fread(),都是无关紧要的。 如果您也需要流式传输在其他浏览器上工作,您可以使用 XMLHttpRequest 对其进行封装。

这是一个适用于基于 Chromium 的浏览器的示例:

<?php
//stream.php

//disable output buffering
while(@ob_end_clean());
ob_implicit_flush(true);

if(empty($_SERVER['QUERY_STRING'])){
    //simulate a lengthy process
    file_put_contents(__FILE__.'.txt',$z='');
    for($i=0; $i<10; $i++){
        sleep(1);
        echo $s = "<div>{$i}</div>\n";
        file_put_contents(__FILE__.'.txt',$z.=$s);
    }
}else{
    //delay 500ms
    usleep(500000);
    echo file_get_contents(__FILE__.'.txt');
}

HTML 对应的版本使其可以与其他浏览器一起使用:

<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1" charset="utf-8"></head>
<body>
<div id='stream'></div>
<script>
var active = true;
xhr('/stream.php',function(){ active = false; });
update();

function update(r){
    if(r) document.getElementById('stream').innerHTML = r.responseText;
    if(active) xhr('/stream.php?stream',update);
}

function xhr(url,callback){
    var r = new XMLHttpRequest();
    r.open('GET',url);
    r.onreadystatechange = function(){ if(r.readyState==4) callback(r); };
    r.send();
}
</script>
</body>
</html>

您可以使用与我提到的相同的机制来创建进度条。