尽早关闭连接
我正在尝试进行AJAX调用(通过JQuery),这将启动一个相当长的过程。我希望脚本仅发送一个指示进程已启动的响应,但是JQuery在PHP脚本运行完成之前不会返回响应。
我已经尝试过使用"关闭"标头(如下),以及输出缓冲了。似乎都不起作用。有什么猜想吗?还是我需要在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( '[email protected]', "okay I'm done", 'Yup, all done.' ); ?>
解决方案
我们可以尝试执行多线程。
我们可以制作一个脚本来进行系统调用(使用shell_exec),该脚本使用该脚本调用php二进制文件作为参数来完成工作。但是我认为这不是最安全的方法。也许我们可以通过更改php进程和其他内容来整理内容
另外,在phpclasses上有一个可以做到这一点的类http://www.phpclasses.org/browse/package/3953.html。但我不知道实施的细节
问题可以通过在php中进行一些并行编程来解决。我几周前在这里问了一个问题:如何在PHP应用程序中使用多线程
并得到了很好的答案。我特别喜欢一个。作者参考了PHP中的Easy Parallel Processing(2008年9月; johnlim),该教程实际上可以很好地解决问题,因为我已经使用它来解决了几天前出现的类似问题。
假设我们具有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进行ping操作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时,我们应该迅速获得以下响应,而无需等待
ping命令完成需要15秒。
start bgtest.php output:Array ( [0] => starting bgping [1] => ending bgping ) result:0 end bgtest.php
ping命令现在应该在服务器上运行。我们可以运行PHP脚本来代替ping命令:
php -n -f largejob.php > dump.txt &
希望这可以帮助!
好的,所以基本上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更改为在主脚本设置中定义的回调方法,以接收数据并对其执行操作。希望这可以帮助。干杯,摩根。
另一种解决方案是将作业添加到队列中,并创建一个cron脚本来检查并运行新作业。
最近,我不得不这样做,以避开共享主机exec()等人施加的限制,因为Web服务器运行的PHP已被禁用,但可以在Shell脚本中运行。
以下PHP手册页(包括用户注释)建议了有关如何在不结束PHP脚本的情况下关闭与浏览器的TCP连接的多种说明:
- 连接处理文档
据说它比发送close标头还需要更多。
然后,OP确认:是的,这成功了:指向复制在此处的用户注释#71172(2006年11月):
Closing the users browser connection whilst keeping your php script running has been an issue since [PHP] 4.1, when the behaviour of register_shutdown_function() was modified so that it would not automatically close the users connection. sts at mail dot xubion dot hu Posted the original solution: <?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"); ?> Which works fine until you substitute phpinfo() for echo('text I want user to see'); in which case the headers are never sent! The solution is to explicitly turn off output buffering and clear the buffer prior to sending your header information. Example: <?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'); ?> Just spent 3 hours trying to figure this one out, hope it helps someone :) Tested in: IE 7.5730.11 Mozilla Firefox 1.81
随后在2010年7月,在一个相关的答案中,Arctic Fire随后将两个跟进的用户注释链接到了上面的一个:
- 连接处理用户注释#89177(2009年2月)
- 连接处理用户注释#93441(2009年9月)
有必要发送以下两个标头:
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