Web 服務器執行一個指令碼,可能幾毫秒就完成,也可能幾分鐘都完不成。如果程式執行緩慢,使用者可能沒有耐心等下去,就關閉瀏覽器了。
而有的時候,我們更本不關心這些耗時的指令碼的執行結果,但卻還要等他執行完返回,才能繼續下一步。
那麼有沒有什麼辦法,只是簡單的觸發調用這些耗時的指令碼然後就繼續下一步,讓這些耗時的指令碼在服務端慢慢執行?
接下來,我將使用fscokopen來實現這一功能。
PHP是支援socket編程的,就是fsockopen, 在以前做CMS的時候,我也曾經用過它做過smtp發信。
fscokopen返回一個到遠程主機串連的控制代碼。你可以像使用fopen返回的控制代碼一樣,對她進行寫fwrite,讀取fgets, fread等操作。
我們的非同步PHP,主要想要的效果就是,觸發一個PHP指令碼,然後立即返回,留它在伺服器端慢慢執行。前面我也寫過一篇文章討論過這個問題。
那麼,我們就可以使用fsockopen串連到本機伺服器,觸發指令碼執行,然後立即返回,不等待指令碼執行完成。
function triggerRequest($url, $post_data = array(), $cookie = array())…{ $method = "GET"; //可以通過POST或者GET傳遞一些參數給要觸發的指令碼 $url_array = parse_url($url); //擷取URL資訊,以便平湊HTTP HEADER $port = isset($url_array['port'])? $url_array['port'] : 80; $fp = fsockopen($url_array['host'], $port, $errno, $errstr, 30); if (!$fp) …{ return FALSE; } $getPath = $url_array['path'] ."?". $url_array['query']; if(!empty($post_data))…{ $method = "POST"; } $header = $method . " " . $getPath; $header .= " HTTP/1.1\r\n"; $header .= "Host: ". $url_array['host'] . "\r\n "; //HTTP 1.1 Host域不能省略 /**//*以下頭資訊域可以省略 $header .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13 \r\n"; $header .= "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,q=0.5 \r\n"; $header .= "Accept-Language: en-us,en;q=0.5 "; $header .= "Accept-Encoding: gzip,deflate\r\n"; */ $header .= "Connection:Close\r\n"; if(!empty($cookie))…{ $_cookie = strval(NULL); foreach($cookie as $k => $v)…{ $_cookie .= $k."=".$v."; "; } $cookie_str = "Cookie: " . base64_encode($_cookie) ." \r\n";//傳遞Cookie $header .= $cookie_str; } if(!empty($post_data))…{ $_post = strval(NULL); foreach($post_data as $k => $v)…{ $_post .= $k."=".$v."&"; } $post_str = "Content-Type: application/x-www-form-urlencoded\r\n";//POST資料 $post_str .= "Content-Length: ". strlen($_post) ." \r\n";//POST資料的長度 $post_str .= $_post."\r\n\r\n "; //傳遞POST資料 $header .= $post_str; } fwrite($fp, $header); //echo fread($fp, 1024); //我們不關心伺服器返回 fclose($fp); return true;}
現在,就可以通過這個函數來觸發一個PHP指令碼的執行,然後函數就會返回。 我們就可以接著執行下一步操作了。
還有一個問題就是,當用戶端中斷連線以後。也就是triggerRequest發送請求後,立即關閉了串連,那麼可能會引起伺服器端正在執行的指令碼退出。
在 PHP 內部,系統維護著串連狀態,其狀態有三種可能的情況:
* 0 – NORMAL(正常)
* 1 – ABORTED(異常退出)
* 2 – TIMEOUT(逾時)
當 PHP 指令碼正常地運行 NORMAL 狀態時,串連為有效。當用戶端中斷串連時,ABORTED 狀態的標記將會被開啟。遠程用戶端串連的中斷通常是由使用者點擊 STOP 按鈕導致的。當連線時間超過 PHP 的時限(請參閱 set_time_limit() 函數)時,TIMEOUT 狀態的標記將被開啟。
可以決定指令碼是否需要在用戶端中斷串連時退出。有時候讓指令碼完整地運行會帶來很多方便,即使沒有遠程瀏覽器接受指令碼的輸出。預設的情況是當遠程用戶端串連 中斷時指令碼將會退出。該處理過程可由 php.ini 的 ignore_user_abort 或由 Apache .conf 設定中對應的“php_value ignore_user_abort”以及 ignore_user_abort() 函數來控制。如果沒有告訴 PHP 忽略使用者的中斷,指令碼將會被中斷,除非通過 register_shutdown_function() 設定了關閉觸發函數。通過該關閉觸發函數,當遠端使用者點擊 STOP 按鈕後,指令碼再次嘗試輸出資料時,PHP 將會檢測到串連已被中斷,並調用關閉觸發函數。
指令碼也有可能被內建的指令碼計時器中斷。預設的逾時限制為 30 秒。這個值可以通過設定 php.ini 的 max_execution_time 或 Apache .conf 設定中對應的“php_value max_execution_time”參數或者 set_time_limit() 函數來更改。當計數器逾時的時候,指令碼將會類似於以上串連中斷的情況退出,先前被註冊過的關閉觸發函數也將在這時被執行。在該關閉觸發函數中,可以通過調 用 connection_status() 函數來檢查逾時是否導致關閉觸發函數被調用。如果逾時導致了關閉觸發函數的調用,該函數將返回 2。
需要注意的一點是 ABORTED 和 TIMEOUT 狀態可以同時有效。這在告訴 PHP 忽略使用者的退出操作時是可能的。PHP 將仍然注意使用者已經中斷了串連但指令碼仍然在啟動並執行情況。如果到了啟動並執行時間限制,指令碼將被退出,設定過的關閉觸發函數也將被執行。在這時會發現函數 connection_status() 返回 3。
所以還在要觸發的指令碼中指明:
ignore_user_abort(TRUE); //如果用戶端中斷連線,不會引起指令碼abort.set_time_limit(0);//取消指令碼執行延時上限
或者,也可以使用:
register_shutdown_function(callback fuction[, parameters]);//註冊指令碼退出時執行的函數