最近在考慮項目爲用戶羣發郵件時想到了這個方法,覺得還有用,所以記錄下來(項目情況是:用戶要根據實際情況給他的客戶發郵件,他的客戶的數量是成千上萬,根據情況選好客戶,寫好郵件點發送後要等着所有這些郵件發完不太現實,所以考慮後臺運行),這個方法使用到的是HTTP的特性,先整理一下思路:
1.HTTP是無狀態的
2.HTTP是請求-應答模式
3.HTTP是建立在TCP之上的(TCP建立在IP之上,每次請求瀏覽器都會使用一個隨機的端口與服務器上的web端口(如80)建立socket連接,瀏覽器的隨機端口可以通過$_SERVER查看到)
4.瀏覽器在請求一個web資源時(本文指PHP文件)會等待Web服務器的響應(本文中指Apache)直到響應結束
5.如果在等待的過程中用戶點擊了瀏覽器上的停止按鈕,瀏覽器會關掉TCP連接,也就是中止當前的HTTP的請求-應答過程,根據對TCP的理解,這個中止應該是向服務器端發送了一條TCP指令
6.底層連接TCP斷掉,當前未完成的響應當然也就輸出不到瀏覽器上了
上面幾條都好理解,但第4點還有細節:
web服務器的響應http頭中有一個頭信息:Connection 會告知瀏覽器連接的保持情況,一般情況下都是:
Connection:keep-alive
並且還有另外一個頭說了要保持多久:Keep-Alive:300。
那如果服務器的響應中說連接已經關閉(connection: close)了會發生什麼呢?瀏覽器會停止等待響應,(rfc 2616)
那現在如果用戶請求的PHP輸出HTTP頭:Connection:close。並把這些頭輸出到瀏覽器,然後再繼續執行後面的代碼,會是什麼效果呢?
反應到瀏覽器上就是頁面請求完了,但是php並沒有執行完,也就是將繼續執行,這就實現了瀏覽器及所請求的php異步執行的效果
例子:
ob_end_clean();#清除之前的緩衝內容,這是必需的,如果之前的緩存不爲空的話,裏面可能有http頭或者其它內容,導致後面的內容不能及時的輸出
header("Connection: close");#告訴瀏覽器,連接關閉了,這樣瀏覽器就不用等待服務器的響應
#可以發送200狀態碼,以這些請求是成功的,要不然可能瀏覽器會重試,特別是有代理的情況下
ob_start();#開發當前代碼緩衝
//{{邏輯代碼
echo "一些處理";
//邏輯代碼}}
//下面輸出http的一些頭信息
$size=ob_get_length();
header("Content-Length: $size");
ob_end_flush();#輸出當前緩衝
flush();#輸出PHP緩衝
#休眠PHP,也就是當前PHP代碼的執行停止,1秒鐘後PHP被喚醒,
#PHP喚醒後,繼續執行下面的代碼,但這個時候上面代碼的結果已經輸出瀏覽器了,
#也就是瀏覽器從HTTP頭中知道了服務端關閉了連接,瀏覽器將不在等待服務器的響應,
#反應給客戶的就是頁面不會顯示處於加載狀態中,換句話說用戶可以關掉當前頁面,或者關掉瀏覽器,
#PHP喚醒後繼續執行下面的代碼,這也就實現了PHP後臺執行的效果,
#休眠的作用只是讓php先把前面的輸出作完,不要急於馬上執行下面的代碼,休息一下而已,也就是說下面的代碼
#執行的時候前面的輸出應該到達瀏覽器了
sleep(1);
echo '這裏的輸出用戶看不到,後臺運行的';
//下面代碼的任何輸出都不會輸出給瀏覽器,因爲http連接已經關了,
//所以下面的代碼的執行屬於後臺運行的
set_time_limit(0);#不受時間限制
$f = fopen('1.txt','a+');
for($i=0;$i<1000;$i++){
if (fwrite($f,$i."
") === FALSE) {
echo "Cannot write to file ($filename)";
}
}
fclose($f);
其它情況:這種做法是讓PHP主動告訴瀏覽器結束對話,這個過程應該是很快的,PHP收到請求後馬上發送http給瀏覽器,但有時候的情況是PHP要先做一些事情,然後在把連接斷掉的http響應返回給瀏覽器,但如果這個時候出現了網絡或者其它一些意外情況導致了瀏覽器關掉了或者失去與服務器了連接了,PHP的響應頭輸出到不了瀏覽器上,PHP還會繼續執行嗎?
與瀏覽器作請求-應答這個過程的是Web服務器,如果請求的是PHP或者其它服務端語言,根據服務器的配置(loadmodule addtype這些)這些資源的請求會轉到對應的語言處理器上,如所有的.php訪問都會由PHP解析執行,並把執行的結果返回給 Apache,apache在返回給瀏覽器,但如果這些響應輸出不到瀏覽器,apache會通知PHP,PHP就會中止當前請求文件的執行。
也就是說如果一個請求的過程中用戶關掉了瀏覽器,或者點停止按鈕的話,所請求的php代碼可能就會只執行到一半,沒有執行完,如果這個時候是在做一些數據庫的寫操作,數據就可能沒有寫完全。但也只是有輸出的時候PHP纔會收到客戶端已經中止的通知,如果php沒有任何的輸出,就算瀏覽器關了,PHP代碼也會完全執行完,那什麼時候PHP會輸出呢?通常PHP有一個輸出緩衝,緩衝區滿後就輸出或者程序正常結束時也輸出。
爲什麼有輸出的時候PHP才知道瀏覽器是否是退出了,大概是因爲Apache輸出失敗才反饋給PHP,如果沒有輸出,PHP就在一邊默默的執行,Apache不通知它
在這種情況下如果PHP仍然要繼續執行,可以使用PHP的一些連接控制函數來忽略客戶端退出連接的情況:
ignore_user_abort,該函數表示客戶端斷掉後是否要中止PHP的執行。默認是中止
換句話說,在請求的過程中瀏覽器是否非正常退出PHP是可以知道的,可能通過connection_status()來得到連接狀態:
0 – 正常
1 – 中止
2 – PHP執行超時
這三個狀態可以疊加,也就是可以有 3 – 中止+PHP執行超時
問題是我們在什麼時候調用connection_status()來得到連接的狀態呢,在一般的代碼中調用,得到的都是0,但如果瀏覽器中止了,php的執行也中止了,在一般的代碼中這個函數不能很好的看到預期的結果,PHP提供了一個hook:register_shutdown_function該方法用於註冊請求結束時的回調,不管請求是正常結束還是異常結束,只要PHP在執行這個回調是一定會調用到。可以在該方法中查看 connection_status()返回值,
function shutdown(){
$f = fopen('1.txt','a+');
fwrite($f,connection_status());
}
register_shutdown_function('shutdown');
while(1){
#如果注掉,用戶點了停止,PHP也會執行到超時,文件1.txt中寫入的是2 PHP執行超時
#如果不注掉,用戶點了停止,文件1.txt中寫入的是1 - 用戶中止
echo ++$i."<br>";