在流中读取实时整个输出
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>
您可以使用与我提到的相同的机制来创建进度条。
注意:这个 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>
您可以使用与我提到的相同的机制来创建进度条。