1. 程式人生 > >http 瀏覽器主動斷開連線 與 php主動斷開連線

http 瀏覽器主動斷開連線 與 php主動斷開連線

摘要:事件起因是因為平時在開發中遇到的疑惑。一次是瀏覽器客戶端主動斷開了連線後,發現伺服器端的php指令碼還在執行,以至於不知道怎樣讓指令碼停下來。還有一次是有需求讓php指令碼主動斷開連線,然後後續指令碼繼續執行(一個耗時任務),所以有了這篇部落格。

一、瀏覽器主動斷開連線

  在常用的LAMP組合下,我們認為,瀏覽器訪問一個php指令碼,指令碼開始執行,指令碼輸出內容,並結束執行,apache響應http,瀏覽器收到http響應,顯示結果。
  下來考慮下特殊的情況。
  1、瀏覽器傳送http請求,php執行了一個耗時任務(20s)(假設php的set_time_limit設定的是30s),在此期間瀏覽器無響應,使用者點選瀏覽器X,瀏覽器主動斷開連線,php指令碼是否還繼續執行。
  假設耗時任務是:計算fib(25),瀏覽器測試響應需要時間1.15s,每執行一次耗時任務,寫檔案Log寫一次,執行10次耗時任務,在執行第5次的時候,客戶端主動斷開連線,觀察情況。
程式碼如下:

<?php
for ($i=0; $i < 10; $i++) { 
    fib(25);
    setLog(date('H:i:s'));
}

function fib($n = 3){
    if($n == 0){
        return 1;
    }
    if($n == 1){
        return 1;
    }
    return fib($n - 1) + fib($n -2);
}

function setLog( $massage, $path=''){
    $log_path = empty($path)?'./log_'
.date('Y-m-d').'.log':$path; $time = date('Y-m-d H:i:s'); $error_page = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; file_put_contents($log_path, "LOG TIME:".$time.PHP_EOL, FILE_APPEND); file_put_contents($log_path, "LOG URL:".$error_page.PHP_EOL, FILE_APPEND); if(is_array($massage
)){ $massage = json_encode($massage); } file_put_contents($log_path, "LOG MESSAGE:".$massage.PHP_EOL.PHP_EOL, FILE_APPEND); } ?>

  瀏覽器在執行到5.44s的時候斷開了連線。
  日誌顯示:指令碼執行完了10次迴圈。
  這與我們之前認為的不一樣;

  2、優化一下,看到網上說,php判斷客戶端連線是否斷開,是在php往客戶端輸出內容的時候判斷的,那麼我們把測試程式碼修改一下:

<php
for ($i=0; $i < 10; $i++) { 
    fib(25);
    setLog(date('H:i:s'));
    echo "hello";
}

//這裡省略了fib和setLog函式
?>

  再次測試一下,發現和上一次的測試結果是一樣的。究其原因:php往客戶端輸出內容的時候,要有3個緩衝階段,分別是:
  php buffer => web server buffer => browser buffer
  只有當緩衝區滿了的時候才會輸出到客戶端,這其實就是,後端每隔一段時間輸出內容到前端的原理。當然也是可以控制當緩衝區沒有滿的時候,也讓輸出到客戶端。

  3、再修改測試程式碼,讓輸出客戶端的內容足夠大:

<?php
$re = "";
for($i=0; $i < 10000; $i++){
    $re .= "aa";
}
for ($i=0; $i < 10; $i++) { 
    fib(25);
    setLog(date('H:i:s'));
    echo $re;
}
//這裡省略了fib和setLog函式
?>

  這次再測試,就會發現瀏覽器會隔一段時間就收到一些相應,而不是之前的demo,需要指令碼完全執行完才輸出內容到客戶端。同時,這個時候關閉客戶端連線,伺服器端當再次向客戶端輸出內容的時候,就會檢查客戶端連線已經斷開了,這個時候指令碼就會停止運行了。這是我們想要的測試結果。

  4、再修改測試程式碼,這次不讓一次輸出一個很大的內容,而是有意操作緩衝區內容,讓雖然不夠從緩衝區輸出到客戶端的內容提前輸出到客戶端。
測試程式碼:

for ($i=0; $i < 10; $i++) { 
    fib(25);
    setLog(date('H:i:s'));
    echo "hello " . date('H:i:s') . "<br>";
    ob_flush();
    flush();
}
//這裡省略了fib和setLog函式

小結:

  • 原則上客戶端主動斷開連線,php指令碼即停止執行;
  • 但是前提是php知道客戶端斷開連線是怎麼知道的,只有當php輸出內容到客戶端(不是php緩衝區、不是web server緩衝區),php才知道客戶端連線中斷了,才會停止執行;
  • php輸出內容到客戶端,有兩種方式。一是填滿內容到緩衝區自動傳送到客戶端;二是使用ob_flush,flush函式主動將緩衝區內容沖刷給客戶端;
  • php指令碼執行還受到內部的指令碼計時器限制,可以在php.ini或者宿主apache配置檔案中配置,或者指令碼中通過set_time_limt函式設定;
  • 當客戶端主動斷開連線,而php指令碼沒有停止執行的時候,還要受限制於指令碼計時器;
  • 當php指令碼設定ignore_user_abort(true); 則即使客戶端連線斷開,且php輸出內容到客戶端知道了客戶端連線斷開,也不會停止指令碼執行;
  • php內部,系統維護的連線狀態,可以通過函式connection_status的返回值檢查,0 : normal; 1 : aborted(斷開連線); 2 : timeout; 改狀態的檢測也是需要php指令碼輸出內容到客戶端才會知道,否則一直都是0;
  • 另外還有一個函式也可以檢測客戶端連線是否斷開(connection_aborted),0正常,1斷開。
  • 有個奇怪的問題是,當客戶端連線已經斷開,php指令碼輸出兩次後,狀態位才變成1;

二、php伺服器端主動斷開連線

  要讓php主動斷開連線,要使用http響應頭裡面的content-length和connection兩個欄位,意義分別為:

  • content-length,當客戶端收到的響應頭content-length,則當相應體收到指定大小後,就會斷開與伺服器的連線;
  • connection,當客戶端收到響應頭connection的值為close或者keep-alive,決定關閉當前tcp連線或者繼續使用當前連線作下一次請求;
  • 測試發現,當只指定conetent-length的時候也能達到php主動斷開連線;
  • 其實說是php主動斷開連線,其實是php通知客戶端主動斷開的連線;

示例程式碼:

<?php
echo "hello world";

test();

for ($i=0; $i < 10; $i++) { 
    fib(25);
    setLog(date('H:i:s'));
}

function test(){
    $size = ob_get_length();
    header("content-length:" . $size);
    //header("connection:close");
    ob_flush();
    flush();
}
//這裡省略了fib和setLog函式
?>



<完>