0%

解决PHP flush 无效的问题

php flush的问题已经碰到过两次了,一次是在做毕设期间,由于需要php去执行一个非常大的数据处理进程,想要显示一个实施进度。目前这次是需要php拉起一个python进程去对一个文件进行处理,想要显示一个文件状态。

由于涉及Apache、php、浏览器,无法准确定位原因。每次都耗费了大量的时间,同时网上现有的解决方案几乎都无法正确执行。

环境

Apache/2.4.27 (Win64)+PHP 7.0.22 (cli) (built: Aug 1 2017 13:56:43) ( ZTS )+Chrome版本 63.0.3239.132(正式版本) (32 位)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header('Content-Type: text/HTML; charset=utf-8');
ob_start();
set_time_limit(0);
for($i=0; $i<10; $i++){
# for Microsoft Internet Explorer
# Microsoft Internet Explorer 只有当接受到的256个字节以后才开始显示该页面,
# 所以必须发送一些额外的空格来让这些浏览器显示页面内容
$flush = str_pad ('', 1000 );
echo $flush;

echo "console.log(".$i.');';
ob_flush();
flush();
sleep(1);
}
?>

上述代码适用于直接输出结果到页面,而不能返回结果给ajax。

在Ajax访问时不成功的原因是:请求都没有完成,XMLHttpRequest组件只负责交换数据,不负责处理数据。处理数据的代码要等到通讯结束后才执行。如果Ajax不以XMLHttpRequest 做传输载体,而以 iframe 做载体的话,上述代码是可以成功的。

实现实时输出的方案

1
提示: 从以下php代码中可以看到每次循环都执行了sleep(1),如果sleep的时间过短例如sleep(0.1)则以下代码并不能良好的执行,仍存在同时返回多条信息的情景。为了应对这种情景我们仍需要一些hack操作,如果状态较少或者循环次数较少则可在每次状态改变并且echo信息之后执行sleep(1),如果状态相应过快或者循环次数较多时,需要改造前端程序,使其具备同时处理多条数据(信息)的能力。

AJAX - XHR Method

Demo

这里采用标准的XHR方法,不是等待readyState == 4时响应请求而是在readyState == 3就对请求进行处理。

1
2
3
4
5
6
ReadyState, holds the status of the XMLHttpRequest
0: request not initialized //未初始化请求
1: server connection established //建立服务器连接
2: request received //收到请求
3: processing request //处理请求
4: request finished and response is ready //请求已完成,响应已准备就绪

对应的js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
if (!window.XMLHttpRequest){
alert("Your browser does not support the native XMLHttpRequest object.");
return;
}
try{
var xhr = new XMLHttpRequest();
xhr.previous_text = '';

xhr.onerror = function() { alert("[XHR] Fatal Error."); };
xhr.onreadystatechange = function() {
try{
if (xhr.readyState == 4){
alert('[XHR] Done')
}
else if (xhr.readyState > 2){
var new_response = xhr.responseText.substring(xhr.previous_text.length);
var result = JSON.parse( new_response );

document.getElementById("divProgress").innerHTML += result.message + '
';
document.getElementById('progressor').style.width = result.progress + "%";

xhr.previous_text = xhr.responseText;
}
}
catch (e){
alert("[XHR STATECHANGE] Exception: " + e);
}
};
xhr.open("GET", "ajax_stream.php", true);
xhr.send();
}
catch (e){
alert("[XHR REQUEST] Exception: " + e);
}

php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set_time_limit(0); 
ob_implicit_flush(true);
for($i = 0; $i < 10; $i++){
//Hard work!!
sleep(1);
$p = ($i+1)*10; //Progress
$response = array( 'message' => $p . '% complete. server time: ' . date("h:i:s", time()),
'progress' => $p);

echo json_encode($response);
}

sleep(1);
$response = array( 'message' => 'Complete',
'progress' => 100);
echo json_encode($response);

Iframe Method

Demo

这个方法比较老,但胜在有效。通过创建一个ifream来执行某个php文件避免等待数据传输结束

1
2
3
4
5
6
ifrm = document.createElement("IFRAME"); 
ifrm.setAttribute("src", "ajax_stream.php");
ifrm.style.width = 0+"px";
ifrm.style.height = 0+"px";
ifrm.style.border = 0;
document.body.appendChild(ifrm);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_time_limit(0); 
ob_implicit_flush(true);
for($i = 0; $i < 10; $i++){
//Hard work!!
sleep(1);
$p = ($i+1)*10; //Progress
$message = $p . '% complete. server time: ' . date("h:i:s", time());
$progress = $p;
echo '';
}
sleep(1);
$message = 'Complete';
$progress = 100;

echo '';

参考 & 引用(ps 谷歌大法好)

http://blog.csdn.net/u011832039/article/details/51387548
https://segmentfault.com/q/1010000008402305/a-1020000008408369
http://www.webhostingtalk.com/showthread.php?t=1138631
http://stratosprovatopoulos.com/web-development/php/ajax-progress-php-script-without-polling/