【问题标题】:PHP abort infinite loop on user disconnectPHP在用户断开连接时中止无限循环
【发布时间】:2014-03-25 13:46:06
【问题描述】:

我正在用 PHP 做一个小实验。我有一个名为“infinity.txt”的文件,在该文件中,我每 0.25 秒写入一个递增数字。

while(true){

    file_put_contents('infinity.txt', ++$i.PHP_EOL, FILE_APPEND);
    usleep(250 * 1000);

}

这很好用,但是当我关闭浏览器中的选项卡时,脚本会继续运行。浏览器请求中止不是Ctrl. + C,所以这并不奇怪,但我仍然想知道当用户断开连接时是否可以中止无限循环,或者是否有任何方法可以查看用户是否仍然连接.

顺便说一句,事先调用register_shutdown_function 是完全没有用的,即使链接函数内部有die()

更新:在我看来,使用connection_aborted() 函数可能会完成一些事情。

更新 2:我已将代码更改为如下所示,但是,无限循环并没有取消:

while(true){

    if(connection_aborted()){

        file_put_contents('infinity.txt', 'CONNECTION ABORTED.', FILE_APPEND);
        die();

    }

    file_put_contents('infinity.txt', ++$i.PHP_EOL, FILE_APPEND);
    usleep(250 * 1000);

}

更新 3:我现在在每次迭代中回显和刷新一些文本,但仍然无济于事:

while(true){

    echo '0';
    ob_flush();
    flush(); // necessary for proper checking

    if(connection_aborted()){

        file_put_contents('infinity.txt', 'CONNECTION ABORTED.', FILE_APPEND);
        die();

    }

    file_put_contents('infinity.txt', ++$i.PHP_EOL, FILE_APPEND);
    usleep(250 * 1000);

}

更新 4:上一个更新中显示的代码在重新启动 Apache 时出于某种奇怪的原因开始工作,而我没有更改 php.ini 中的任何设置。最终的帮助是在一开始就添加了ignore_user_abort(true);

【问题讨论】:

    标签: php browser infinite-loop abort


    【解决方案1】:

    这样做(我在 cmets 中留下了解释):

    ignore_user_abort(1); //we'll handle it by ourselves
    header('Transfer-Encoding:chunked'); //HTTP 1.1: do not capture immediately (bin)
    flush();
    ob_flush();
    
    $i = 0;
    set_time_limit(0);
    while(1)
    {
        echo "0"; //do this: sending data to dead TCP connection will fail
        flush();
        ob_flush();
        if(connection_status()!=0)
        {
    
            file_put_contents('infinity.txt', 'CONNECTION ABORTED.', FILE_APPEND);
            echo "0\r\n\r\n"; //stream termination packet (double \r\n according to proto)
            flush();
            ob_flush();
            exit();
    
        }
    
        file_put_contents('infinity.txt', ++$i.PHP_EOL, FILE_APPEND);
        usleep(250 * 1000);
    
    }
    

    【讨论】:

    • 我刚刚重新启动了 Apache,在 Firefox 中检查了它,然后又回到了 Chrome。它开始使用我在第三次更新中使用的代码。我很困惑,但谢谢,最终,是ob_flush(); flush(); 成功了!很抱歉让您感到困惑。
    • 不仅如此。不同之处在于您的第三个代码没有Transfer-Encoding 标头(因此您将收到0 因为flush() 调用)。您还需要正确发送终止数据包以结束流
    • 不,脚本可以在没有 Transfer-Encoding 标头的情况下工作。该标题无关紧要。它正在添加您建议的ignore_user_abort(true);,这可以解决问题。谢谢!
    • 它有效。但会向您发送数据(即浏览器会在收到数据后显示)
    • 没错,但我从来没有打算让浏览器在发送的那一刻看到输出;)
    【解决方案2】:

    在您链接的同一 connection_aborted() 页面上查看 this comment

    像 PHP 这样的 Web 服务器的默认过程是创建整个 HTML 页面,并在完成后将其发送到浏览器。在浏览器向服务器请求文件后,在文件发送之前没有进一步的通信。

    但是,您可以使用 ob_flush() 和相关的输出控制函数来更改此行为。这会导致 PHP 生成一定数量的 HTML,然后将其发送到浏览器。处理时的这种通信允许您之前尝试的connection_aborted() 工作。

    编辑添加: 真高兴你做到了!我还为寻找这个的其他人创建了一些工作测试代码:

    <?php 
    
    @apache_setenv('no-gzip', 1);
    @ini_set('zlib.output_compression', 0);
    @ini_set('implicit_flush', 1);
    for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); }
    ob_implicit_flush(1);
    ignore_user_abort(true);
    ob_start();
    
    for ($i=1;$i<10;$i++) {
        sleep(2);
        echo "done pass ". $i . ".<br />";
        echo str_pad('',4096)."\n";          
        ob_flush();
        if(connection_aborted()){
            file_put_contents('test.txt', "CONNECTION ABORTED on pass $i.".PHP_EOL, FILE_APPEND);
            exit(); 
        }
        else {
            file_put_contents('test.txt', "successful pass    on pass $i.".PHP_EOL, FILE_APPEND);
        }
    }
    

    【讨论】:

    • 查看我的第三次更新。不幸的是,它仍然不起作用。
    • 第一步是确保渐进式渲染正常工作:stackoverflow.com/questions/3133209/… 在浏览器中看到输出后,测试 connection_aborted() 部分。我必须包含填充以使其像这里描述的那样工作:us1.php.net/flush#54841 实际上这可能是一个很好的测试代码。
    • 我刚刚重新启动了 Apache,在 Firefox 中检查了它,然后又回到了 Chrome。它开始使用我在第三次更新中使用的代码。我很困惑,但谢谢,最终,是 ob_flush(); flush(); 成功了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多