Read realtime whole output in stream

2019-05-04 10:01发布

Note: this post have difference with this post whose accepted answer just read each line a time.

I have to slice 3D models for 3D printing on the server side, the process will cost some time. So I have to show the process for the user, I use the redis to store the process. I want to refresh the process every 0.5 seconds. For example, sleep 0.5 sec, read all the content in the pip and process it each time.

For now I have tryed the below two, the first one will hold until it finished. the second use while is not a proper way, it will keep writing the redis will cause the client read process request hold to the end.

I've tryed these two:

The first will hold until the command finished.

$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.
}

and the second implemented as this post will each time one line.

  $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();
        }
    }

3条回答
虎瘦雄心在
2楼-- · 2019-05-04 10:21

use fread() to replace fgets().

查看更多
Explosion°爆炸
3楼-- · 2019-05-04 10:23

Take a look at the Symfony component Process. It provides easy async process, and real-time process output, among other things. For example:

$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();
    }
}
查看更多
Animai°情兽
4楼-- · 2019-05-04 10:37

You should disable output buffering first by adding this line to the start of your code:

while(@ob_end_clean());

With that line added, your second code should be able to stream.

Unfortunately, even with output buffering disabled, the result of output streaming is browser dependent. I tested several browsers, both desktop and mobile, and found that output streaming works in Chromium-based browsers, but doesn't work in Firefox.

Because it is browser dependent, the functions you use, be it fgets() or fread(), are irrelevant. If you need the streaming to work on other browsers as well, you can encapsulate it with XMLHttpRequest.

Here is an example that works with Chromium-based browsers:

<?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');
}

The HTML counterpart makes it work with other browsers:

<!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>

You can use the same mechanism to create a progress bar like I mentioned.

查看更多
登录 后发表回答