我已经尝试使用“关闭”标头(如下),并带有输出缓冲;似乎都不起作用。有什么猜想吗?还是我需要在JQuery中做这件事?
<?php
echo( "We'll email you as soon as this is done." );
header( "Connection: Close" );
// do some stuff that will take a while
mail( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' );
?>
#1 楼
以下PHP手册页(包括用户注释)建议了有关如何在不结束PHP脚本的情况下关闭与浏览器的TCP连接的多种说明:连接处理文档
它比发送一个关闭的报头还需要更多的信息。
OP然后确认:是的,这样做的窍门是:指向用户注释#71172(2006年11月)从[PHP] 4.1开始,当修改
register_shutdown_function()
的行为使其不会自动关闭时,关闭用户浏览器连接并保持php脚本运行一直是一个问题。用户连接。邮件点xubion点hu上的sts发布了原始解决方案:
<?php
header("Connection: close");
ob_start();
phpinfo();
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>
直到您用
phpinfo()
代替echo('text I want user to see');
都可以正常工作在这种情况下,永远不会发送标头!解决方案是在发送标头信息之前显式关闭输出缓冲并清除缓冲区。示例:
<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
ob_start();
echo('Text the user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // Strange behaviour, will not work
flush(); // Unless both are called !
// Do processing here
sleep(30);
echo('Text user will never see');
?>
花了3个小时试图弄清楚这一点,希望对别人有帮助:)
在以下位置进行了测试:
IE 7.5730.11
Mozilla Firefox 1.81
于2010年7月在北极发布了相关答案然后,Fire将上面的两个后续的用户注释链接在一起:
连接处理用户注释#89177(2009年2月)
连接处理用户注释- note#93441(2009年9月)
评论
是的,这确实有效:php.net/manual/en/features.connection-handling.php#71172
– Eric_WVGG
08-09-26在20:14
作者和@Timbo White,是否可以在不知道内容大小的情况下尽早关闭连接? IE,而不必在关闭前捕获内容。
–散装
2014年11月11日在18:05
黑客和糟糕的Web浏览器仍然可以忽略连接关闭的HTTP标头,并获取其余的输出。确保接下来出现的内容不敏感。也许是ob_start();抑制一切:p
– hanshenrik
15年6月2日在8:46
添加fastcgi_finish_request();据说在上述方法不起作用时可以成功关闭连接。但是,就我而言,这阻止了我的脚本继续执行,因此请谨慎使用。
–埃里克·杜贝(EricDubé)
16年6月16日在2:22
@RichardSmith因为Connection:close头可以被堆栈中的其他软件覆盖,例如,对于CGI,反向代理可以覆盖(我观察到nginx的行为)。请参阅@hanshenrik的答案。通常,Connection:close是在客户端执行的,不应视为对此问题的答案。连接应从服务器端关闭。
– 7heo.tk
16年7月21日在15:24
#2 楼
必须发送这两个标头:Connection: close
Content-Length: n (n = size of output in bytes )
由于您需要知道输出的大小,因此需要缓冲输出,然后将其刷新到浏览器:
// buffer all upcoming output
ob_start();
echo "We'll email you as soon as this is done.";
// get the size of the output
$size = ob_get_length();
// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');
// flush all output
ob_end_flush();
ob_flush();
flush();
// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();
/******** background process starts here ********/
此外,如果您的Web服务器在输出上使用自动gzip压缩(例如,使用mod_deflate的Apache),则此操作将不起作用,因为输出更改,并且Content-Length不再准确。禁用特定脚本的gzip压缩。
有关更多详细信息,请访问http://www.zulius.com/how-to/close-browser-connection-continue-execution
评论
如果您的服务器压缩输出,则可以使用header(“ Content-Encoding:none \ r \ n”);将其禁用。这样,Apache将不会对其进行压缩。
– GDmac
2012-3-18在16:03
@GDmac谢谢!我暂时无法使用它,但是禁用压缩可以解决问题。
–反应堆
2013年9月16日16:02
ob_flush()是不必要的,实际上会导致通知未能刷新缓冲区。我将其取出,效果很好。
–利维
2013年9月29日14:02
我发现ob_flush()行是必需的。
– Deebster
2014年4月4日在11:46
#3 楼
您可以将Fast-CGI与PHP-FPM结合使用以使用fastcgi_end_request()
函数。这样,您可以在响应已发送到客户端的情况下继续进行一些处理。如何使用fastcgi_finish_request()的示例(2010年11月)
您可以在PHP手册中找到此文件:FastCGI流程管理器(FPM);但是该功能没有在手册中进一步记录。以下是PHP-FPM的摘录:PHP FastCGI Process Manager Wiki:
fastcgi_finish_request()
范围:php函数
类别:优化
此功能使您可以加快某些php查询的实施。当脚本执行过程中有一些不影响服务器响应的操作时,可以进行加速。例如,可以在页面形成并传递到Web服务器之后将会话保存在memcached中。
fastcgi_finish_request()
是php功能,可停止响应输出。 Web服务器立即开始“缓慢而悲伤地”将响应传输到客户端,同时php可以在查询的上下文中做很多有用的事情,例如保存会话,转换下载的视频,处理各种内容。统计信息等。fastcgi_finish_request()
可以调用正在执行的关闭功能。 要避免该问题,可以在
fastcgi_finish_request()
调用之前或之后立即调用flush
:ignore_user_abort(true);
fastcgi_finish_request();
评论
这是实际的答案!
– Kirill Titov
15年7月9日在8:07
如果您使用的是php-fpm-只需使用此功能-则无需考虑标头和其他所有内容。节省了我很多时间!
–罗斯
16年7月28日在18:42
#4 楼
完整版本:ignore_user_abort(true);//avoid apache to kill the php running
ob_start();//start buffer output
echo "show something to user";
session_write_close();//close session file on server side to avoid blocking other requests
header("Content-Encoding: none");//send header to avoid the browser side to take content as gzip format
header("Content-Length: ".ob_get_length());//send length header
header("Connection: close");//or redirect to some url: header('Location: http://www.google.com');
ob_end_flush();flush();//really send content, can't change the order:1.ob buffer to normal buffer, 2.normal buffer to output
//continue do something on server side
ob_start();
sleep(5);//the user won't wait for the 5 seconds
echo 'for diyism';//user can't see this
file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();
评论
在哪个意义上完成?哪个问题需要您完成可接受的答案脚本(哪个?),并且您的哪些配置差异使此操作必要?
– hakre
13年8月24日在6:39
这行:header(“ Content-Encoding:none”); ->非常重要。
– Bobby Tables
2013年9月25日14:36在
谢谢,这是此页面上唯一可行的解决方案。应该将其作为答案。
–user494599
16-09-15在9:11
#5 楼
更好的解决方案是派生后台进程。在unix / linux上非常简单:<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php dude@thatplace.com >/dev/null &");
?>
您应该看一下这个问题以获取更好的示例:
PHP执行后台进程
评论
取决于您要做什么。如果将当前请求期间确定的某些数据写入高速缓存,则分叉几乎肯定不是更好的解决方案。即使在OP的情况下,如果其他解决方案都是可靠的,则与将大量数据传递给派生进程相比,它们也可以节省一些服务器CPU周期。
–杰克
10月4日23:16
#6 楼
假设您具有Linux服务器和root用户访问权限,请尝试此操作。这是我找到的最简单的解决方案。为以下文件创建一个新目录,并赋予其完整权限。 (稍后我们将使其更安全。)
mkdir test
chmod -R 777 test
cd test
将其放在名为
bgping
的文件中。echo starting bgping
ping -c 15 www.google.com > dump.txt &
echo ending bgping
注意
&
。当当前进程进入echo命令时,ping命令将在后台运行。它将对www.google.com进行15次ping操作,这大约需要15秒。
执行可执行文件。
chmod 777 bgping
将其放在名为
bgtest.php
的文件中。<?php
echo "start bgtest.php\n";
exec('./bgping', $output, $result)."\n";
echo "output:".print_r($output,true)."\n";
echo "result:".print_r($result,true)."\n";
echo "end bgtest.php\n";
?>
当您请求bgtest时。在浏览器中的php中,您应该迅速获得以下响应,而无需等待大约15秒即可完成ping命令。
start bgtest.php
output:Array
(
[0] => starting bgping
[1] => ending bgping
)
result:0
end bgtest.php
ping命令应现在正在服务器上运行。代替ping命令,您可以运行PHP脚本:
php -n -f largejob.php > dump.txt &
希望有帮助!
#7 楼
这是对Timbo代码的修改,可用于gzip压缩。// buffer all upcoming output
if(!ob_start("ob_gzhandler")){
define('NO_GZ_BUFFER', true);
ob_start();
}
echo "We'll email you as soon as this is done.";
//Flush here before getting content length if ob_gzhandler was used.
if(!defined('NO_GZ_BUFFER')){
ob_end_flush();
}
// get the size of the output
$size = ob_get_length();
// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');
// flush all output
ob_end_flush();
ob_flush();
flush();
// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();
/******** background process starts here ********/
评论
你是上帝。我已经工作了2天,试图解决这个问题。它对我的本地开发人员有效,但对主机无效。我被水管了。你救了我。谢谢!!!!
–乍得考德威尔
4月13日在16:14
#8 楼
我在共享主机上,并且已将fastcgi_finish_request
设置为完全退出脚本。我也不喜欢connection: close
解决方案。使用它会强制为后续请求建立单独的连接,从而浪费额外的服务器资源。我阅读了Transfer-Encoding: cunked
维基百科文章,并了解到0\r\n\r\n
终止了响应。我尚未在浏览器的版本和设备上进行全面测试,但是它可以在我当前使用的所有4种浏览器上运行。// Disable automatic compression
// @ini_set('zlib.output_compression', 'Off');
// @ini_set('output_buffering', 'Off');
// @ini_set('output_handler', '');
// @apache_setenv('no-gzip', 1);
// Chunked Transfer-Encoding & Gzip Content-Encoding
function ob_chunked_gzhandler($buffer, $phase) {
if (!headers_sent()) header('Transfer-Encoding: chunked');
$buffer = ob_gzhandler($buffer, $phase);
return dechex(strlen($buffer))."\r\n$buffer\r\n";
}
ob_start('ob_chunked_gzhandler');
// First Chunk
echo "Hello World";
ob_flush();
// Second Chunk
echo ", Grand World";
ob_flush();
ob_end_clean();
// Terminating Chunk
echo "\x30\r\n\r\n";
ob_flush();
flush();
// Post Processing should not be displayed
for($i=0; $i<10; $i++) {
print("Post-Processing");
sleep(1);
}
评论
多亏了您的良好回答,我才意识到使用连接:关闭是多么愚蠢(和不必要)。我猜有些人对服务器的基本知识并不熟悉。
–贾斯汀
18年3月13日在9:15
@Justin我很久以前写了这个。再次查看它,我应该指出,可能有必要将这些块填充到4KB。我似乎记得有些服务器在达到最低要求之前不会刷新。
–散装
18年3月13日在13:21
@skibulk有些需要64K的(未压缩??)填充,例如这是FcgidOutputBufferSize的默认设置,不能在某些共享服务器上覆盖。
–杰克
10月5日,0:47
#9 楼
您可以尝试进行多线程处理。您可以编写一个脚本来进行系统调用(使用shell_exec),该脚本使用该脚本调用php二进制文件作为参数来完成工作。但是我认为这不是最安全的方法。也许您可以通过更改php进程和其他内容来整理内容。或者,在phpclasses上有一个可以做到这一点的类http://www.phpclasses.org/browse/package/3953.html。但是我不知道实现的细节
评论
而且,如果您不想等待该过程完成,请使用&字符在后台运行该过程。
–利亚姆
08年9月26日在9:32
#10 楼
TL; DR答案:ignore_user_abort(true); //Safety measure so that the user doesn't stop the script too early.
$content = 'Hello World!'; //The content that will be sent to the browser.
header('Content-Length: ' . strlen($content)); //The browser will close the connection when the size of the content reaches "Content-Length", in this case, immediately.
ob_start(); //Content past this point...
echo $content;
//...will be sent to the browser (the output buffer gets flushed) when this code executes.
ob_end_flush();
ob_flush();
flush();
if(session_id())
{
session_write_close(); //Closes writing to the output buffer.
}
//Anything past this point will be ran without involving the browser.
功能答案:
ignore_user_abort(true);
function sendAndAbort($content)
{
header('Content-Length: ' . strlen($content));
ob_start();
echo $content;
ob_end_flush();
ob_flush();
flush();
}
sendAndAbort('Hello World!');
//Anything past this point will be ran without involving the browser.
#11 楼
您的问题可以通过在php中进行一些并行编程来解决。几周前,我在这里问了一个问题:如何在PHP应用程序中使用多线程,并获得了不错的答案。我特别喜欢一个。笔者参考了PHP中的Easy Parallel Processing(轻松并行处理)(2008年9月; johnlim),该教程实际上可以很好地解决您的问题,因为我已经使用它来解决了几天前出现的类似问题。 br />
#12 楼
Joeri Sebrechts的答案很接近,但是它破坏了在您希望断开连接之前可能已缓冲的所有现有内容。它没有正确调用ignore_user_abort
,从而使脚本过早终止。 diyism的答案很好,但不能普遍适用。例如。一个人可能有更多或更少的输出缓冲区,而该缓冲区无法解决该问题,因此它可能无法在您的情况下正常工作,您也不知道为什么。此功能允许您随时断开连接( (只要尚未发送标头),并保留到目前为止生成的内容。默认情况下,额外的处理时间是无限的。
function disconnect_continue_processing($time_limit = null) {
ignore_user_abort(true);
session_write_close();
set_time_limit((int) $time_limit);//defaults to no limit
while (ob_get_level() > 1) {//only keep the last buffer if nested
ob_end_flush();
}
$last_buffer = ob_get_level();
$length = $last_buffer ? ob_get_length() : 0;
header("Content-Length: $length");
header('Connection: close');
if ($last_buffer) {
ob_end_flush();
}
flush();
}
如果您还需要额外的内存,请在调用此函数之前分配它。
#13 楼
给mod_fcgid用户的注意事项(请您自担风险)。但是,如果使用mod_fcgid,则可能会发现此解决方案无法单独使用。换句话说,当调用flush函数时,与客户端的连接不会关闭。可能要怪mod_fcgid的
FcgidOutputBufferSize
配置参数。我在以下文章中找到了这个技巧:此回复Travers Carter和
此博客帖子Seumas Mackinnon。
阅读完上述内容后,您可能会得出一个结论,即快速的解决方案是添加该行(请参阅最后的“示例虚拟主机”):
FcgidOutputBufferSize 0
在您的Apache配置文件(例如,httpd.conf),FCGI配置文件(例如,fcgid.conf)或虚拟主机文件(例如,httpd-vhosts.conf)中。
在上面的(1)中,提到了一个名为“ OutputBufferSize”的变量。这是(2)中提到的
FcgidOutputBufferSize
的旧名称(有关mod_fcgid,请参见Apache网页中的升级说明)。详细信息和第二个解决方案
/>以上解决方案禁用了mod_fcgid对整个服务器或特定虚拟主机执行的缓冲。这可能会导致您的网站的性能下降。另一方面,由于PHP本身执行缓冲,因此情况可能并非如此。
如果您不想禁用mod_fcgid的缓冲,则还有另一种解决方案...您可以强制使用此缓冲刷新代码。
下面的代码仅基于Joeri Sebrechts提出的解决方案来实现:
<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
ob_start();
echo('Text the user will see');
echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer.
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // Strange behaviour, will not work
flush(); // Unless both are called !
// Do processing here
sleep(30);
echo('Text user will never see');
?>
所添加的代码行本质上是在填充mod_fcgi的缓冲区,从而迫使其刷新。之所以选择数字“ 65537”,是因为
FcgidOutputBufferSize
变量的默认值为“ 65536”,如相应指令的Apache Web页面中所述。因此,如果您的环境中设置了另一个值,则可能需要相应地调整此值。我的环境
WampServer 2.5
Apache 2.4。 9
PHP 5.5.19 VC11,x86,无线程安全
mod_fcgid / 2.3.9
Windows 7 Professional x64
虚拟主机示例
<VirtualHost *:80>
DocumentRoot "d:/wamp/www/example"
ServerName example.local
FcgidOutputBufferSize 0
<Directory "d:/wamp/www/example">
Require all granted
</Directory>
</VirtualHost>
评论
我尝试了许多解决方案。这是使用mod_fcgid的唯一解决方案。
– Tsounabe
18年5月23日在18:40
#14 楼
这对我有用//avoid apache to kill the php running
ignore_user_abort(true);
//start buffer output
ob_start();
echo "show something to user1";
//close session file on server side to avoid blocking other requests
session_write_close();
//send length header
header("Content-Length: ".ob_get_length());
header("Connection: close");
//really send content, can't change the order:
//1.ob buffer to normal buffer,
//2.normal buffer to output
ob_end_flush();
flush();
//continue do something on server side
ob_start();
//replace it with the background task
sleep(20);
#15 楼
好的,所以基本上jQuery进行XHR请求的方式,甚至ob_flush方法也将不起作用,因为您无法在每个onreadystatechange上运行一个函数。 jQuery检查状态,然后选择要采取的正确操作(完成,错误,成功,超时)。尽管我找不到参考资料,但我记得听说它不适用于所有XHR实现。我认为一种对您有用的方法是在ob_flush和永久帧轮询之间进行交叉。 />
<?php
function wrap($str)
{
return "<script>{$str}</script>";
};
ob_start(); // begin buffering output
echo wrap("console.log('test1');");
ob_flush(); // push current buffer
flush(); // this flush actually pushed to the browser
$t = time();
while($t > (time() - 3)) {} // wait 3 seconds
echo wrap("console.log('test2');");
?>
<html>
<body>
<iframe src="ob.php"></iframe>
</body>
</html>
由于脚本是内联执行的,因此刷新缓冲区后,您就可以执行了。为了使此功能有用,请将console.log更改为在主脚本设置中定义的回调方法,以接收数据并对其执行操作。希望这可以帮助。干杯,摩根。
#16 楼
另一种解决方案是将作业添加到队列中,并创建一个cron脚本来检查新作业并运行它们。我最近不得不这样做,以避开共享主机施加的限制- exec()等已被网络服务器运行的PHP禁用,但可以在Shell脚本中运行。
#17 楼
如果flush()
功能不起作用。您必须在php.ini中设置下一个选项,例如:output_buffering = Off
zlib.output_compression = Off
#18 楼
最新的工作解决方案 // client can see outputs if any
ignore_user_abort(true);
ob_start();
echo "success";
$buffer_size = ob_get_length();
session_write_close();
header("Content-Encoding: none");
header("Content-Length: $buffer_size");
header("Connection: close");
ob_end_flush();
ob_flush();
flush();
sleep(2);
ob_start();
// client cannot see the result of code below
#19 楼
在尝试了该线程的许多不同解决方案之后(没有一个对我有用),我在PHP.net官方页面上找到了解决方案:function sendResponse($response) {
ob_end_clean();
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
ignore_user_abort(true);
ob_start();
echo $response; // Actual response that will be sent to the user
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
if (ob_get_contents()) {
ob_end_clean();
}
}
评论
您是否使用ob_flush()刷新了输出缓冲区,但没有用?