我正在尝试进行AJAX调用(通过JQuery),这将启动一个相当长的过程。我希望脚本只是发送一个响应,表明进程已经启动,但是在PHP脚本运行完毕之前,JQuery不会返回响应。
我尝试过“关闭”标题(下图),还有输出缓冲;似乎都不起作用。任何猜测?或者这是我需要在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.' );
?>
答案 0 :(得分:81)
以下PHP手册页(包括用户注释)提供了关于如何在不结束PHP脚本的情况下关闭与浏览器的TCP连接的多条指令:
据说它需要的不仅仅是发送一个关闭标题。
自[PHP] 4.1以来,关闭用户浏览器连接同时保持php脚本运行是一个问题,当
register_shutdown_function()
的行为被修改时,它不会自动关闭用户连接。邮件点xubion dot hu发布原始解决方案:
<?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月晚些时候,在related answer Arctic Fire中,然后将另外两个用户注释与上述用户注释相关联:
答案 1 :(得分:54)
有必要发送这两个标题:
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
答案 2 :(得分:15)
完整版:
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();
答案 3 :(得分:15)
您可以使用Fast-CGI和PHP-FPM来使用fastcgi_end_request()
function。通过这种方式,您可以在响应已发送到客户端时继续进行一些处理。
您可以在PHP手册中找到:FastCGI Process Manager (FPM);但该功能并未在手册中进一步说明。这里摘录自PHP-FPM: PHP FastCGI Process Manager Wiki:
范围:php函数 类别:优化
此功能允许您加快某些php查询的实现。当脚本执行过程中的操作不影响服务器响应时,可以进行加速。例如,在页面形成并传递到Web服务器之后,可以在memcached中保存会话。 fastcgi_finish_request()
是一个php功能,可以停止响应输出。 Web服务器立即开始向客户端“缓慢而悲伤地”传输响应,并且php同时可以在查询的上下文中执行许多有用的操作,例如保存会话,转换下载的视频,处理各种统计等等。
fastcgi_finish_request()
可以调用执行关闭函数。
答案 4 :(得分:4)
假设您拥有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命令将在后台运行。
它会ping 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时,您应该快速得到以下响应,而不必等待 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 &
希望这有帮助!
答案 5 :(得分:4)
以下是对使用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 ********/
答案 6 :(得分:4)
更好的解决方案是分叉后台进程。在unix / linux上它是相当直接的:
<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php dude@thatplace.com >/dev/null &");
?>
您应该查看此问题以获得更好的示例:
答案 7 :(得分:3)
我在共享主机上,fastcgi_finish_request
设置为完全退出脚本。我也不喜欢connection: close
解决方案。使用它会强制为后续请求单独连接,从而导致额外的服务器资源耗费。我阅读了Transfer-Encoding: cunked
Wikipedia Article并了解到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);
}
答案 8 :(得分:2)
您可以尝试进行多线程处理。
你可以用一个脚本调用一个系统调用(使用shell_exec)来调用带有脚本的php二进制文件来完成你的工作作为参数。但我不认为这是最安全的方式。也许你可以通过chrooting php进程和其他东西来搞砸东西
或者,phpclasses中有一个类http://www.phpclasses.org/browse/package/3953.html。但我不知道实施的具体细节
答案 9 :(得分:2)
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.
答案 10 :(得分:1)
你的问题可以通过在php中进行一些并行编程来解决。几周前我在这里问了一个问题:How can one use multi threading in PHP applications
得到了很好的答案。我非常喜欢一个。作者提出了一个参考to the Easy Parallel Processing in PHP (Sep 2008; by johnlim) tutorial,它可以很好地解决你的问题,因为我已经用它来处理几天前出现的类似问题。
答案 11 :(得分:1)
Joeri Sebrechts' answer已关闭,但它会破坏您希望断开连接之前可能缓冲的所有现有内容。它没有正确调用ignore_user_abort
,允许脚本过早终止。 diyism's answer很好,但一般不适用。例如。一个人可能有更多或更少的输出缓冲区,答案无法处理,所以它可能根本不适用于你的情况,你不会知道原因。
此功能允许您随时断开连接(只要尚未发送标头)并保留您目前为止生成的内容。默认情况下,额外处理时间不受限制。
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();
}
如果您还需要额外的内存,请在调用此函数之前进行分配。
答案 12 :(得分:1)
注意mod_fcgid用户(请自行承担使用风险)。
Joeri Sebrechts的接受答案确实有效。但是,如果您使用 mod_fcgid ,您可能会发现此解决方案无法自行运行。换句话说,当调用 flush 函数时,与客户端的连接不会被关闭。
mod_fcgid 的FcgidOutputBufferSize
配置参数可能是罪魁祸首。我找到了这个提示:
阅读上述内容后,您可能会得出结论:快速解决方案是添加该行(请参阅末尾的“示例虚拟主机”):
FcgidOutputBufferSize 0
在您的Apache配置文件(例如,httpd.conf),FCGI配置文件(例如,fcgid.conf)或虚拟主机文件(例如,httpd-vhosts.conf)中。
在上面的(1)中,提到了名为“OutputBufferSize”的变量。这是(2)中提到的
FcgidOutputBufferSize
的旧名称(请参阅upgrade notes in the Apache web page for mod_fcgid)。
上述解决方案禁用 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 page for the corresponding directive中所述。因此,如果在您的环境中设置了其他值,则可能需要相应地调整此值。
<VirtualHost *:80>
DocumentRoot "d:/wamp/www/example"
ServerName example.local
FcgidOutputBufferSize 0
<Directory "d:/wamp/www/example">
Require all granted
</Directory>
</VirtualHost>
答案 13 :(得分:1)
这对我有用
//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);
答案 14 :(得分:0)
在尝试了许多不同的解决方案之后(在他们都没有为我工作之后),我在PHP.net官方网页上找到了解决方案:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct Numbers
{
char cell[21];
char home[21];
char business[21];
};
int main (void)
{
// Declare variables here:
char prompt[10];
char prompt1;
struct Numbers numbers;
numbers.cell[0] = '\0';
numbers.home[0] = '\0';
numbers.business[0] = '\0';
printf("Do you want to enter a cell phone number? (y or n):\n");
scanf(" %c", &prompt1);
if (prompt1 == 'y' || prompt1 == 'Y')
{
printf("Please enter the contact's cell phone number:\n");
scanf("%s", numbers.cell);
}
printf("Do you want to enter a home phone number? (y or n):\n");
scanf(" %c", &prompt1);
if (prompt1 == 'y' || prompt1 == 'Y')
{
printf("Please enter the contact's home phone number:\n");
scanf("%s", numbers.home);
}
printf("Do you want to enter a business phone number? (y or n):\n");
scanf("%s", &prompt);
if (*prompt == 'y' || *prompt == 'Y')
{
printf("Please enter the contact's business phone number:\n");
scanf("%s", numbers.business);
}
printf("\n");
printf("Phone Numbers:");
printf("\n");
printf("Cell phone number: %s", numbers.cell);
printf("\n");
printf("Home phone number: %s", numbers.home);
printf("\n");
printf("Business phone number: %s", numbers.business);
printf("\n");
printf("Structure test for Name, Address, and Numbers Done!");
return 0;
}
答案 15 :(得分:0)
最新工作解决方案
// 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
答案 16 :(得分:0)
如果flush()
功能不起作用。您必须在 php.ini 中设置下一个选项,如:
output_buffering = Off
zlib.output_compression = Off
答案 17 :(得分:0)
另一种解决方案是将作业添加到队列中并创建一个cron脚本,用于检查新作业并运行它们。
最近我不得不这样做以规避共享主机强加的限制 - 对于由网络服务器运行的PHP禁用了exec()等,但是可以在shell脚本中运行。
答案 18 :(得分:0)
好的,基本上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更改为主脚本设置中定义的回调方法,以接收数据并对其进行操作。希望这可以帮助。干杯,摩根。