【问题标题】:PHP connection_aborted() not working correctlyPHP connection_aborted() 无法正常工作
【发布时间】:2011-09-16 21:34:45
【问题描述】:

我有以下代码:

ignore_user_abort(true);
while(!connection_aborted()) {
    // do stuff
}

根据 PHP 文档,这应该一直运行到连接关闭,但由于某种原因,它不会,而是一直运行直到脚本超时。我在网上浏览了一些建议添加

echo chr(0);
flush();

进入循环,但这似乎也没有任何作用。更糟糕的是,如果我把它保留为

while(true) {
    // do stuff
}

客户端断开连接后,PHP 仍会继续运行脚本。有谁知道如何让这个工作?是否有我在某处缺少的 php.ini 设置?

如果重要的话,我正在运行 PHP 5.3.5。提前致谢!

【问题讨论】:

    标签: php timeout connection infinite-loop


    【解决方案1】:

    我参加这个聚会有点晚了,但我刚刚遇到了这个问题,并把它弄明白了。这里发生了很多事情——这里提到了其中的一些: PHP doesn't detect connection abort at all

    要点:为了让connection_aborted() 工作,PHP 需要尝试向客户端发送数据。

    输出缓冲区

    如前所述,PHP 在尝试实际向客户端发送数据之前不会检测到连接已断开。这不像echo 那样简单,因为echo 将数据发送到任何可能存在的output buffers,并且在这些缓冲区足够满之前,PHP 不会尝试真正的发送。我不会详细介绍输出缓冲,但值得一提的是,可以有多个嵌套的缓冲区。

    无论如何,如果你想测试 connection_abort(),你必须首先结束所有的缓冲区:

    while (ob_get_level()){ ob_end_clean(); }
    

    现在,只要您想测试连接是否中止,您就必须尝试向客户端发送数据:

    echo "Something.";
    flush();
    
    // returns expected value...
    // ... but only if ignore_user_abort is false!
    connection_aborted(); 
    

    忽略用户中止

    这是一个非常重要的设置,它决定了当上面的flush() 被调用并且用户中止连接(例如:点击浏览器中的停止按钮)时 PHP 将做什么。

    如果true,脚本将愉快地运行。 flush() 基本上什么都不做。

    如果false,按照默认设置,执行将立即以下列方式停止:

    • 如果 PHP 尚未关闭,它将开始关闭 过程。

    • 如果 PHP 已经关闭,它将退出任何关闭 它所在的功能并继续下一个。

    析构函数

    如果你想在用户中止连接时做一些事情,你需要做三件事:

    1. 检测用户中止连接。这意味着您必须定期尝试向用户发送flush,如上文所述。清除所有输出缓冲区,回显,刷新。

      一个。如果ignore_connection_aborted 为真,则需要在每次刷新后手动测试connection_aborted()

      b.如果ignore_connection_aborted 为假,则调用flush 将导致关闭过程开始。 您必须特别小心,不要在关闭函数中引起flush,否则 PHP 将立即停止执行该函数并继续执行下一个关闭函数。

    把它们放在一起

    综上所述,让我们举个例子,检测用户点击“STOP”并执行操作。

    class DestructTester {
        private $fileHandle;
    
        public function __construct($fileHandle){
            // fileHandle that we log to
            $this->fileHandle = $fileHandle;
            // call $this->onShutdown() when PHP is shutting down.
            register_shutdown_function(array($this, "onShutdown"));
        }
    
        public function onShutdown() {
            $isAborted = connection_aborted();
            fwrite($this->fileHandle, "PHP is shutting down. isAborted: $isAborted\n");
    
            // NOTE
            // If connection_aborted() AND ignore_user_abort = false, PHP will immediately terminate
            // this function when it encounters flush. This means your shutdown functions can end
            // prematurely if: connection is aborted, ignore_user_abort=false, and you try to flush().
            echo "Test.";
            flush();
            fwrite($this->fileHandle, "This was written after a flush.\n");
        }
        public function __destruct() {
            $isAborted = connection_aborted();
            fwrite($this->fileHandle, "DestructTester is getting destructed. isAborted: $isAborted\n");
        }
    }
    
    // Create a DestructTester
    // It'll log to our file on PHP shutdown and __destruct().
    $fileHandle = fopen("/path/to/destruct-tester-log.txt", "a+");
    fwrite($fileHandle, "---BEGINNING TEST---\n");
    $dt = new DestructTester($fileHandle);
    
    // Set this value to see how the logs end up changing
    // ignore_user_abort(true);
    
    // Remove any buffers so that PHP attempts to send data on flush();
    while (ob_get_level()){
        ob_get_contents();
        ob_end_clean();
    }
    
    // Let's loop for 10 seconds
    //   If ignore_user_abort=true:
    //      This will continue to run regardless.
    //   If ignore_user_abort=false:
    //      This will immediate terminate when the user disconnects and PHP tries to flush();
    //      PHP will begin its shutdown process.
    // In either case, connection_aborted() should subsequently return "true" after the user
    // has disconnected (hit STOP button in browser), AND after PHP has attempted to flush().
    $numSleeps = 0;
    while ($numSleeps++ < 10) {
        $connAbortedStr = connection_aborted() ? "YES" : "NO";
        $str = "Slept $numSleeps times. Connection aborted: $connAbortedStr";
        echo "$str<br>";
        // If ignore_user_abort = false, script will terminate right here.
        // Shutdown functions will being.
        // Otherwise, script will continue for all 10 loops and then shutdown.
        flush();
    
        $connAbortedStr = connection_aborted() ? "YES" : "NO";
        fwrite($fileHandle, "flush()'d $numSleeps times. Connection aborted is now: $connAbortedStr\n");
        sleep(1);
    }
    echo "DONE SLEEPING!<br>";
    die;
    

    cmets 解释了一切。您可以摆弄ignore_user_abort 并查看日志以了解这会如​​何改变事情。

    我希望这可以帮助任何遇到connection_abortregister_shutdown_function__destruct 问题的人。

    【讨论】:

      【解决方案2】:

      尝试在flush(); 之前使用ob_flush();,一些浏览器在添加一些数据之前不会更新页面。

      尝试做类似的事情

      <? php
      // preceding scripts
      
      ignore_user_abort(true);
      
      $i = 0;
      
      while(!connection_aborted())
      { $i++;
        echo $i;
      
        echo str_pad('',4096); // yes i know this will increase the overhead but that can be reduced afterwords
      
        ob_flush();
      
        flush();
      
        usleep(30000); // see what happens when u run this on my WAMP this runs perfectly
      }
      
      // Ending scripts
      ?>
      

      实际上,Google Chrome 的这段代码存在问题;它不能很好地支持流式传输。

      【讨论】:

        【解决方案3】:

        试试:

            ignore_user_abort(true);
        
            echo "Testing connection handling";
        
            while (1) {
                    if (connection_status() != CONNECTION_NORMAL)
                            break;
                    sleep(1);
                    echo "test";
                    flush();
            }
        

        【讨论】:

        • 不,似乎也不起作用。值得注意的是,当我在浏览器中运行它时,我看不到“测试”每秒出现一次。换句话说,它看起来不像是在刷新脚本,这让我相信它可能是我缺少的 php.ini 设置?
        • 你的 php.ini 中的output_buffering 设置是什么?
        • output_buffering = 4096 我应该尝试将其设置为“关闭”吗?
        • 是的,我认为您可能需要在进行更改后重新启动您的网络服务器软件。
        • -1 这不是最好的答案。正如 OP 所提到的,php.ini 是这里问题的原因。最好的答案是@JS_Riddler 的这个线程中的stackoverflow.com/a/58775947/609862。它解释了阻止 connection_aborted() 按预期工作的根本问题
        【解决方案4】:

        根据您的服务器设置,缓冲似乎会导致问题。

        我尝试使用 ob_end_clean 禁用缓冲区,但这还不够,我必须发送一些数据以使缓冲区完全刷新。这是最终为我工作的最终代码。

        set_time_limit(0); // run the delay as long as the user stays connected
        ignore_user_abort(false);
        ob_end_clean();
        echo "\n";
        while ($delay-- > 0 && !connection_aborted())
        {
            echo str_repeat("\r", 1000) . "<!--sleep-->\n";
            flush();
            sleep(1);
        }
        ob_start();
        

        【讨论】:

          猜你喜欢
          • 2015-11-15
          • 1970-01-01
          • 2019-01-13
          • 2021-08-16
          • 2012-10-28
          • 2013-10-13
          • 2012-12-06
          • 1970-01-01
          • 2016-01-19
          相关资源
          最近更新 更多