這篇文章將為大家詳細講解有關PHP異步執行的4種常用方式是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
PHP 異步后臺處理
PHP 作為后臺的接口服務器已經很常見,在實際應用場景中經常需要異步后臺處理。
PHP 當然具有它能作為后臺服務器的優勢之處,但是,在處理一些客戶端并不關心的結果時,就顯出它的弊端了,沒有異步執行的機制。
就比如我們想做一些對于某次客戶端訪問php的性能記錄(包括開始時間、結束時間、此次結果狀態等)的記錄時,客戶端當然想的是php的本次處理能夠早點返回,拿到結果,而如果安裝常規的方案,客戶端就得等php做完性能記錄之后,才能拿到結果。
相當于你去銀行去查你現在的余額,而柜員跑過去跟其他人鬧了一會兒的磕,在來告訴你的結果一樣。
所以,很多時候,就需要一種php能執行異步操作。
PHP 如何實現異步處理呢?
其中一種方案就是利用php的系統調用,開啟新的進程來實現。
php 提供了fsockopen函數,此函數的功能為初始化一個套接字連接到指定主機,默認情況下將以阻塞模式開啟套接字連接。
當然你可以通過stream_set_blocking()將它轉換到非阻塞模式。這是關鍵。
所以,思路就是:開啟一個非阻塞的套接字連接到本機,本機收到之后作一些耗時處理。
類似這樣的處理代碼(文件posttest.php):
$fp = fsockopen($php_Path,80);
if (!$fp) {
LMLog::error("fsockopen:err" );
} else {
$out = "GET /album/action/album_write_friends_thread_record.php?key=&u= HTTP/1.1\r\n";
$out .= "Host: ".$php_Path."\r\n";
$out .= "Connection: Close\r\n\r\n";
stream_set_blocking($fp,true);
stream_set_timeout($fp,1);
fwrite($fp, $out);
usleep(1000);
fclose($fp);
}這里,usleep(1000) 非常關鍵,它能保證這個請求能發出去。
我們在來看處理的代碼邏輯(文件album_write_friends_thread_record.php):
<?php /** * Created by PhpStorm. * User: Administrator * Date: 2016-09-23 * Time: 09:26 */ /** * 客戶端調用服務器接口頁面 * user: guwen */ sleep(20);// 睡眠20s ?>
實際上,我們服務器在執行fsockopen 那段程序時,就不會再等20s之后才能返回給客戶端,
而是發出這個請求之后,即返回客戶端,銷毀進程,而把剩余的工作交由其他進程慢慢做去,這就實現了php的異步。
PHP 異步執行的4種常用方式
客戶端與服務器端是通過HTTP協議進行連接通訊,客戶端發起請求,服務器端接收到請求后執行處理,并返回處理結果。
有時服務器需要執行很耗時的操作,如處理下載、消息下發、郵件發送等,這個操作的結果并不需要返回給客戶端。
但因為php是同步執行的,所以客戶端需要等待服務處理完才可以進行下一步。
因此,對于耗時的操作適合異步執行,服務器接收到請求后,處理完客戶端需要的數據就先返回,剩余耗時的操作再異步在服務器后臺執行。
PHP異步執行的常用方式常見的有以下幾種,可以根據各自優缺點進行選擇:
1. ajax 請求
客戶端頁面采用AJAX技術請求服務器
$.get("doRequest.php", { name: "fdipzone"} );
<img src="doRequest.php?name=fdipzone">優點:最簡單,也最快,就是在返回給客戶端的HTML代碼中,嵌入AJAX調用,或者,嵌入一個img標簽,src指向要執行的耗時腳本。
缺點:一般來說Ajax都應該在onLoad以后觸發,也就是說,用戶點開頁面后,就關閉,那就不會觸發我們的后臺腳本了。
而使用img標簽的話,這種方式不能稱為嚴格意義上的異步執行。用戶瀏覽器會長時間等待php腳本的執行完成,也就是用戶瀏覽器的狀態欄一直顯示還在load。
當然,還可以使用其他的類似原理的方法,比如script標簽等等。
2. popen()函數
該函數打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。
打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。
所以可以通過調用它,但忽略它的輸出。使用代碼如下:
// popen — 打開進程文件指針
resource popen ( string $command , string $mode )
pclose(popen('php /home/fdipzone/doRequest.php &', 'r'));優點:避免了第一個方法的缺點,并且執行速度快。
缺點:這種方法不能通過HTTP協議請求另外的一個WebService,只能執行本地的腳本文件。并且只能單向打開,無法穿大量參數給被調用腳本。并且如果,訪問量很高的時候,會產生大量的進程。如果使用到了外部資源,還要自己考慮競爭。
1)只能在本機執行
2)不能傳遞大量參數
3)訪問量高時會創建很多進程
3. curl 擴展
CURL是一個強大的HTTP命令行工具,可以模擬POST/GET等HTTP請求,然后得到和提取數據,顯示在"標準輸出"(stdout)上面。
設置curl的超時時間 CURLOPT_TIMEOUT 為1 (最小為1),因此客戶端需要等待1秒
代碼如下:
<?php
$ch = curl_init();
$curl_opt = array(
CURLOPT_URL, 'http://www.example.com/doRequest.php'
CURLOPT_RETURNTRANSFER,1,
CURLOPT_TIMEOUT,1
);
curl_setopt_array($ch, $curl_opt);
curl_exec($ch);
curl_close($ch);
?>缺點:如你問題中描述的一樣,由于使用CURL需要設置CUROPT_TIMEOUT為1(最小為1,郁悶)。也就是說,客戶端至少必須等待1秒鐘。
4. fscokopen()函數
fsockopen是最好的,缺點是需要自己拼接header部分。
<?php
$url = 'http://www.example.com/doRequest.php';
$param = array(
'name'=>'fdipzone',
'gender'=>'male',
'age'=>30
);
doRequest($url, $param);
function doRequest($url, $param=array()){
$urlinfo = parse_url($url);
$host = $urlinfo['host'];
$path = $urlinfo['path'];
$query = isset($param)? http_build_query($param) : '';
$port = 80;
$errno = 0;
$errstr = '';
$timeout = 10;
$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
$out = "POST ".$path." HTTP/1.1\r\n";
$out .= "host:".$host."\r\n";
$out .= "content-length:".strlen($query)."\r\n";
$out .= "content-type:application/x-www-form-urlencoded\r\n";
$out .= "connection:close\r\n\r\n";
$out .= $query;
fputs($fp, $out);
fclose($fp);
}
?>注意:當執行過程中,客戶端連接斷開或連接超時,都會有可能造成執行不完整,因此需要加上
ignore_user_abort(true); // 忽略客戶端斷開 set_time_limit(0); // 設置執行不超時
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()函數應該可以滿足您的要求??梢試L試一下。
fscokopen的問題和popen 一樣,并發非常多時會產生很多子進程,當達到apache的連接限制數時,就會掛掉,我問題已經說了這種情況。
PHP 本身沒有多線程的東西,但可以曲線的辦法來造就出同樣的效果,比如多進程的方式來達到異步調用,只限于命令模式。還有一種更簡單的方式,可用于 Web 程序中,那就是用fsockopen()、fputs() 來請求一個 URL 而無需等待返回,如果你在那個被請求的頁面中做些事情就相當于異步了?! ?/p>
關鍵代碼如下:
$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
function asyn_sendmail() {
$fp=fsockopen('localhost',80,&$errno,&$errstr,5);
if(!$fp){
echo "$errstr ($errno)<br />\n";
}
sleep(1);
fputs($fp,"GET /sendmail.php?param=1\r\n"); #請求的資源 URL 一定要寫對
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 的繼續往下執行,表明這一過程是異步的。
關于PHP異步執行的4種常用方式是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。