尽早closures连接

我试图做一个AJAX调用(通过JQuery),将启动一个相当长的过程。 我希望脚本只发送一个响应,指示进程已经开始,但是在PHP脚本运行之前,JQuery不会返回响应。

我已经试过这个与“closures”标题(下),并与输出缓冲; 似乎都没有工作。 任何猜测? 或者这是我需要做的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.' ); ?> 

下面的PHP手册页(包括用户注释)提出了多条关于如何closures到浏览器的TCP连接而不终止PHP脚本的说明:

  • 连接处理文档

据推测,它需要比发送closures标题更多一点。


OP然后确认:是的,这是诀窍: 指向用户注释#71172(2006年11月)在这里复制:

closures用户浏览器连接,同时保持您的PHP脚本运行一直是一个问题,因为[PHP] 4.1,当register_shutdown_function()的行为被修改,以便它不会自动closures用户连接。

sts at mail dot xubion dot hu发表原来的解决scheme:

 <?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"); ?> 

其中工作正常,直到你replacephpinfo() echo('text I want user to see'); 在这种情况下,标题永远不会被发送!

解决scheme是显式closures输出缓冲并清除缓冲区之前发送您的标题信息。 例:

 <?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个小时试图找出这一个,希望它可以帮助一个人:)

经testing:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

后来在2010年7月的一个相关答案中, Arctic Fire将两个进一步的用户注释关联起来,

  • 连接处理用户注释#89177(Feb 2009)
  • 连接处理用户注释#93441(2009年9月)

有必要发送这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

您可以使用Fast-CGI和PHP-FPM来使用fastcgi_end_request()函数 。 这样,当响应已经发送给客户端时,可以继续进行一些处理。

  • 如何使用fastcgi_finish_request()(2010年11月)

您可以在这里的PHP手册中find这个: FastCGI Process Manager(FPM) ; 但是这个function在手册中没有进一步的记载。 这里摘自PHP-FPM:PHP FastCGI Process Manager Wiki :


fastcgi_finish_request()

范围:phpfunction分类:优化

这个function可以让你加快执行一些php的查询。 在脚本执行过程中存在不影响服务器响应的操作时,可以进行加速。 例如,将会话保存在memcached中可能会在页面形成并传递到Web服务器之后发生。 fastcgi_finish_request()是一个php特性,它会停止响应输出。 Web服务器立即开始向客户端“缓慢而可悲地”传输响应,同时php在查询的上下文中可以做很多有用的事情,例如保存会话,转换下载的video,处理各种统计等

fastcgi_finish_request()可以调用执行关机function。

完整版本:

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

假设你有一个Linux服务器和root权限,试试这个。 这是我find的最简单的解决scheme。

为以下文件创build一个新的目录并授予其完全权限。 (以后我们可以使它更安全。)

 mkdir test chmod -R 777 test cd test 

把它放在一个名为bgping的文件中。

 echo starting bgping ping -c 15 www.google.com > dump.txt & echo ending bgping 

注意& 。 ping命令将在后台运行,而当前进程将转到echo命令。 它会ping http://www.google.com 15次,这将需要大约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 & 

希望这可以帮助!

你可以尝试做multithreading。

你可以用一个脚本来调用(使用shell_exec )系统调用脚本的php二进制文件来完成你的工作。 但我不认为这是最安全的方式。 也许你可以通过chroot的PHP过程和其他东西来收紧东西

或者,在phpclasses上有一个类可以完成http://www.phpclasses.org/browse/package/3953.html 。 但是我不知道实施的具体情况

这是对使用gzip压缩的Timbo代码的修改。

 // 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 ********/ 

Joeri Sebrechts的答案很接近,但是在你想断开连接之前,它会破坏所有可能被缓冲的内容。 它不会正确调用ignore_user_abort ,从而使脚本过早终止。 diyism的答案是好的,但不是普遍适用的。 例如,一个人可能有更多或更less的输出缓冲区,这个答案无法处理,所以它可能根本不适用于你的情况,你不知道为什么。

此function允许您随时断开连接(只要标题尚未发送),并保留迄今已生成的内容。 额外的处理时间默认是无限的。

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

如果您还需要额外的内存,请在调用此函数之前分配它。

这对我工作

 //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); 

更好的解决scheme是分叉后台进程。 在unix / linux上相当简单:

 <?php echo "We'll email you as soon as this is done."; system("php somestuff.php dude@thatplace.com >/dev/null &"); ?> 

你应该看看这个问题,以获得更好的例子:

PHP执行后台进程

好吧,基本上jQuery做XHR请求的方式,即使ob_flush方法也不行,因为你无法在每个onreadystatechange上运行一个函数。 jQuery检查状态,然后select正确的操作(完成,错误,成功,超时)。 虽然我无法find一个参考,但我记得听说这不适用于所有的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更改为主脚本安装中定义的callback方法,以接收数据并对其执行操作。 希望这可以帮助。 干杯,摩根。

另一种解决scheme是将作业添加到队列中,并创build一个cron脚本来检查新作业并运行它们。

我最近不得不这样做,以规避由共享主机施加的限制 – exec()等被禁用的PHP运行的PHP服务器,但可以运行在一个shell脚本。

你的问题可以通过在php中进行一些并行编程来解决。 几周前我问了一个关于它的问题: 如何在PHP应用程序中使用multithreading

并得到很好的答案。 我特别喜欢一个。 作者引用了PHP中简单并行处理 (2008年9月,由johnlim)教程 ,它可以很好地解决您的问题,因为我已经使用它来处理前两天出现的类似问题。

请注意mod_fcgid用户(请自行承担风险)。

快速解决scheme

Joeri Sebrechts接受的答案确实是function性的。 但是,如果使用mod_fcgid ,则可能发现此解决scheme无法自行工作。 换句话说,当flush函数被调用时,与客户端的连接不会被closures。

mod_fcgidFcgidOutputBufferSizeconfiguration参数可能是责备。 我发现了这个技巧:

  1. Travers Carter的回复
  2. Seumas Mackinnon的博客文章 。

阅读完上述内容后,您可能会得出结论:快速解决scheme是添加行(请参阅最后的“示例虚拟主机”):

 FcgidOutputBufferSize 0 

(例如httpd.conf),FCGIconfiguration文件(例如fcgid.conf)或虚拟主机文件(例如httpd-vhosts.conf)中。

在上面(1)中,提到名为“OutputBufferSize”的variables。 这是(2)中提到的FcgidOutputBufferSize的旧名称(请参阅Apache网页中有关mod_fcgid的升级说明 )。

细节和第二个解决scheme

上述解决scheme禁用整个服务器或特定虚拟主机的mod_fcgid执行的缓冲。 这可能会导致您的网站的性能损失。 另一方面,这可能不是这样,因为PHP自己执行缓冲。

如果你不想禁用mod_fcgid的缓冲,还有另一种解决办法… 你可以强制这个缓冲区刷新

下面的代码通过build立Joeri Sebrechts提出的解决scheme来做到这一点:

 <?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的缓冲区,从而强制刷新。 因为FcgidOutputBufferSizevariables的默认值是“65536”,所以select的数字是“65537”,如相应指令的Apache网页所述 。 因此,如果您的环境中设置了其他值,则可能需要相应地调整此值。

我的环境

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

我在共享主机上, fastcgi_finish_request被设置为完全退出脚本。 我不喜欢这个connection: close解决scheme。 使用它强制一个单独的连接为后续请求,花费额外的服务器资源。 我阅读了Transfer-Encoding: cunked Wikipedia Article,并了解到0\r\n\r\n终止一个响应。 我还没有彻底testing过这个浏览器版本和设备,但它适用于我目前的所有浏览器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); } 

如果flush()函数不起作用。 你必须在php.ini中设置下面的选项:

 output_buffering = Off zlib.output_compression = Off 

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. 

function回答:

 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. 

最新的工作scheme

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