本篇文章給大家分享了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次的時候,用戶端主動中斷連線,觀察情況。
代碼如下:
<?phpfor ($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往用戶端輸出內容的時候判斷的,那麼我們把測試代碼修改一下:
<phpfor ($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通知用戶端主動斷開的串連;
範例程式碼:
<?phpecho "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函數?>
<完>