浏覽器和伺服器之間隻一種面向無連接配接的HTTP協定進行通訊的,面向無連接配接的程式的特點是用戶端請求服務端,服務端根據請求輸出相應的程式,不能保持持久連接配接。
這樣就出現了一個問題,一個用戶端的相應服務端可能執行1秒也有可能執行1分鐘,這樣浏覽器就會一直處于等待狀态,如果程式執行緩慢,使用者可能就沒耐心關掉了浏覽器。
而有的時候我們不需要關心程式執行的結果,沒有必要這樣浪費時間和耐心等待,那我們就要想出辦法讓程式不收等待在背景靜默執行。
比如現在有一個場景,給1000個使用者發送一封推薦郵件,使用者輸入或者導入郵件賬号了送出伺服器執行發送。
<?php
$count=count($emailarr);
for($i=0;$i<$count;$i++)
{
sendmail(.....);//發送郵件
}
?>
這段代碼使用者體驗極差,也無法實際運用,首先發送這麼多郵件會産生伺服器運作逾時,其實漫長的使用者等待時間會讓使用者對系統産品懷疑和失去信心。但是使用者不需要等待到1000封郵件都發送完畢了才送出發送成功,我們完全可以送出背景後直接給使用者提示發送成功,然後讓背景程式靜默依次發送。
這個時候我們就需要“異步執行”技術來執行代碼,異步執行的特點是背景靜默執行,使用者無需等待代碼的執行結果,使用異步執行的好處:
1.擺脫了應用程式對單個任務的依賴性
2.提高了程式的執行效率
3.提高了程式的擴充性
4.在一定場景提高了使用者體驗
5.因為PHP不支援多線程,使用異步調用的請求多個HTTP的方式達到了程式并行執行效果,但是注意的是請求的HTTP過多的話,會大大加大了系統的開銷
是以對于耗時的操作适合異步執行,伺服器接收到請求後,處理完用戶端需要的資料就傳回,再異步在伺服器執行耗時的操作。
解決方法
fsockopen支援socket程式設計,可以使用fsockopen實作郵件發送等socket程式等等,使用fcockopen需要自己手動拼接出header部分
可以參考: http://cn.php.net/fsockopen/
使用示例如下:
-
$fp = fsockopen("www.34ways.com", 80, $errno, $errstr, 30);
-
if (!$fp) {
-
echo "$errstr ($errno)<br />\n";
-
} else {
-
$out = "GET /index.php / HTTP/1.1\r\n";
-
$out .= "Host: www.34ways.com\r\n";
-
$out .= "Connection: Close\r\n\r\n";
-
fwrite($fp, $out);
-
/*忽略執行結果
-
while (!feof($fp)) {
-
echo fgets($fp, 128);
-
}*/
-
fclose($fp);
-
}
是以總結來說,fscokopen()函數應該可以滿足您的要求。可以嘗試一下。
fscokopen的問題和popen一樣,并發非常多時會産生很多子程序,當達到apache的連接配接限制數時,就會挂掉,我問題已經說了這種情況。
PHP本身沒有多線程的東西,但可以曲線的辦法來造就出同樣的效果,比如多程序的方式來達到異步調用,隻限于指令模式。還有一種更簡單的方式,可用于 Web 程式中,那就是用fsockopen()、fwrite() 來請求一個 URL 而無需等待傳回,如果你在那個被請求的頁面中做些事情就相當于異步了。
關鍵代碼如下:
複制
- $fp=fsockopen('localhost',80,$errno,$errstr,5);
- if(!$fp){
- echo "$errstr ($errno)<br />\n";
- }
- fputs($fp,"GET another_page.php?flag=1\r\n");
- fclose($fp);
上面的代碼向頁面 another_page.php 發送完請求就不管了,用不着等待請求頁面的響應資料,利用這一點就可以在被請求的頁面 another_page.php 中異步的做些事情了。
比如,一個很切實的應用,某個 Blog 在每 Post 了一篇新日志後需要給所有它的訂閱者發個郵件通知。如果按照通常的方式就是:
日志寫完 -> 點送出按鈕 -> 日志插入到資料庫 -> 發送郵件通知 ->
告知撰寫者釋出成功
那麼作者在點送出按鈕到看到成功提示之間可能會等待很常時間,基本是在等郵件發送的過程,比如連接配接郵件服務異常、或器緩慢或是訂閱者太多。而實際上是不管郵件發送成功與否,保證日志儲存成功基本可接受的,是以等待郵件發送的過程是很不經濟的,這個過程可異步來執行,并且郵件發送的結果不太關心或以日志形式記錄備查。
改進後的流程就是:
日志寫完 -> 點送出按鈕 -> 日志插入到資料庫 --->
告知撰寫者釋出成功
└ 發送郵件通知 -> [記下日志]
用個實際的程式來測試一下,有兩個 php,分别是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 來模拟程式執行使用時間。
write.php,執行耗時 1 秒
-
<?php
-
/**
* 耗時異步操作
* @param $url 模闆 子產品名稱/控制器/方法?參數名=參數值
* 嚴格注意url的格式和fsockopen,fwrite,fclose的格式
* */
-
function asyn_sendmail() {
-
$fp=fsockopen('localhost',80,$errno,$errstr,5);
-
if(!$fp){
-
echo "$errstr ($errno)<br />\n";
-
}
-
sleep(1);
-
$out = "GET /sendmail.php?param=1 HTTP/1.1\r\n";
$out .= "Host:localhost\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
-
fclose($fp);
-
}
-
echo time().'<br>';
-
echo 'call asyn_sendmail<br>';
-
asyn_sendmail();
-
echo time().'<br>';
-
?>
sendmail.php,執行耗時 10 秒
複制
- <?php
- //sendmail();
- //sleep 10 seconds
- sleep(10);
- fopen('C:\'.time(),'w');
- ?>
通過頁面通路 write.php,頁面輸出:
1272472697 call asyn_sendmail
1272472698
并且在 C:\ 生成檔案:
1272472708
從上面的結果可知 sendmail.php 花費至少 10 秒,但不會阻塞到 write.php 的繼續往下執行,表明這一過程是異步的。