早点关闭连接

时间:2008-09-26 09:02:01

标签: php jquery ajax

我正在尝试进行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.' );

?>

19 个答案:

答案 0 :(得分:81)

以下PHP手册页(包括用户注释)提供了关于如何在不结束PHP脚本的情况下关闭与浏览器的TCP连接的多条指令:

据说它需要的不仅仅是发送一个关闭标题。


然后OP确认:是的,这就是诀窍: pointing to user-note #71172 (Nov 2006)复制到这里:

  

自[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


fastcgi_finish_request()

范围: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 &");
?>

您应该查看此问题以获得更好的示例:

PHP execute a background process

答案 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配置参数可能是罪魁祸首。我找到了这个提示:

  1. this reply of Travers Carter
  2. this blog post of Seumas Mackinnon
  3. 阅读上述内容后,您可能会得出结论:快速解决方案是添加该行(请参阅末尾的“示例虚拟主机”):

    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)。

    细节&amp;第二种解决方案

    上述解决方案禁用 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中所述。因此,如果在您的环境中设置了其他值,则可能需要相应地调整此值。

    我的环境

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

答案 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更改为主脚本设置中定义的回调方法,以接收数据并对其进行操作。希望这可以帮助。干杯,摩根。