您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)PHP異步執(zhí)行的4種常用方式是什么,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
PHP 異步后臺(tái)處理
PHP 作為后臺(tái)的接口服務(wù)器已經(jīng)很常見,在實(shí)際應(yīng)用場(chǎng)景中經(jīng)常需要異步后臺(tái)處理。
PHP 當(dāng)然具有它能作為后臺(tái)服務(wù)器的優(yōu)勢(shì)之處,但是,在處理一些客戶端并不關(guān)心的結(jié)果時(shí),就顯出它的弊端了,沒有異步執(zhí)行的機(jī)制。
就比如我們想做一些對(duì)于某次客戶端訪問php的性能記錄(包括開始時(shí)間、結(jié)束時(shí)間、此次結(jié)果狀態(tài)等)的記錄時(shí),客戶端當(dāng)然想的是php的本次處理能夠早點(diǎn)返回,拿到結(jié)果,而如果安裝常規(guī)的方案,客戶端就得等php做完性能記錄之后,才能拿到結(jié)果。
相當(dāng)于你去銀行去查你現(xiàn)在的余額,而柜員跑過去跟其他人鬧了一會(huì)兒的磕,在來告訴你的結(jié)果一樣。
所以,很多時(shí)候,就需要一種php能執(zhí)行異步操作。
PHP 如何實(shí)現(xiàn)異步處理呢?
其中一種方案就是利用php的系統(tǒng)調(diào)用,開啟新的進(jìn)程來實(shí)現(xiàn)。
php 提供了fsockopen函數(shù),此函數(shù)的功能為初始化一個(gè)套接字連接到指定主機(jī),默認(rèn)情況下將以阻塞模式開啟套接字連接。
當(dāng)然你可以通過stream_set_blocking()將它轉(zhuǎn)換到非阻塞模式。這是關(guān)鍵。
所以,思路就是:開啟一個(gè)非阻塞的套接字連接到本機(jī),本機(jī)收到之后作一些耗時(shí)處理。
類似這樣的處理代碼(文件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) 非常關(guān)鍵,它能保證這個(gè)請(qǐng)求能發(fā)出去。
我們?cè)趤砜刺幚淼拇a邏輯(文件album_write_friends_thread_record.php):
<?php /** * Created by PhpStorm. * User: Administrator * Date: 2016-09-23 * Time: 09:26 */ /** * 客戶端調(diào)用服務(wù)器接口頁面 * user: guwen */ sleep(20);// 睡眠20s ?>
實(shí)際上,我們服務(wù)器在執(zhí)行fsockopen 那段程序時(shí),就不會(huì)再等20s之后才能返回給客戶端,
而是發(fā)出這個(gè)請(qǐng)求之后,即返回客戶端,銷毀進(jìn)程,而把剩余的工作交由其他進(jìn)程慢慢做去,這就實(shí)現(xiàn)了php的異步。
PHP 異步執(zhí)行的4種常用方式
客戶端與服務(wù)器端是通過HTTP協(xié)議進(jìn)行連接通訊,客戶端發(fā)起請(qǐng)求,服務(wù)器端接收到請(qǐng)求后執(zhí)行處理,并返回處理結(jié)果。
有時(shí)服務(wù)器需要執(zhí)行很耗時(shí)的操作,如處理下載、消息下發(fā)、郵件發(fā)送等,這個(gè)操作的結(jié)果并不需要返回給客戶端。
但因?yàn)閜hp是同步執(zhí)行的,所以客戶端需要等待服務(wù)處理完才可以進(jìn)行下一步。
因此,對(duì)于耗時(shí)的操作適合異步執(zhí)行,服務(wù)器接收到請(qǐng)求后,處理完客戶端需要的數(shù)據(jù)就先返回,剩余耗時(shí)的操作再異步在服務(wù)器后臺(tái)執(zhí)行。
PHP異步執(zhí)行的常用方式常見的有以下幾種,可以根據(jù)各自優(yōu)缺點(diǎn)進(jìn)行選擇:
1. ajax 請(qǐng)求
客戶端頁面采用AJAX技術(shù)請(qǐng)求服務(wù)器
$.get("doRequest.php", { name: "fdipzone"} ); <img src="doRequest.php?name=fdipzone">
優(yōu)點(diǎn):最簡單,也最快,就是在返回給客戶端的HTML代碼中,嵌入AJAX調(diào)用,或者,嵌入一個(gè)img標(biāo)簽,src指向要執(zhí)行的耗時(shí)腳本。
缺點(diǎn):一般來說Ajax都應(yīng)該在onLoad以后觸發(fā),也就是說,用戶點(diǎn)開頁面后,就關(guān)閉,那就不會(huì)觸發(fā)我們的后臺(tái)腳本了。
而使用img標(biāo)簽的話,這種方式不能稱為嚴(yán)格意義上的異步執(zhí)行。用戶瀏覽器會(huì)長時(shí)間等待php腳本的執(zhí)行完成,也就是用戶瀏覽器的狀態(tài)欄一直顯示還在load。
當(dāng)然,還可以使用其他的類似原理的方法,比如script標(biāo)簽等等。
2. popen()函數(shù)
該函數(shù)打開一個(gè)指向進(jìn)程的管道,該進(jìn)程由派生給定的 command 命令執(zhí)行而產(chǎn)生。
打開一個(gè)指向進(jìn)程的管道,該進(jìn)程由派生給定的 command 命令執(zhí)行而產(chǎn)生。
所以可以通過調(diào)用它,但忽略它的輸出。使用代碼如下:
// popen — 打開進(jìn)程文件指針 resource popen ( string $command , string $mode ) pclose(popen('php /home/fdipzone/doRequest.php &', 'r'));
優(yōu)點(diǎn):避免了第一個(gè)方法的缺點(diǎn),并且執(zhí)行速度快。
缺點(diǎn):這種方法不能通過HTTP協(xié)議請(qǐng)求另外的一個(gè)WebService,只能執(zhí)行本地的腳本文件。并且只能單向打開,無法穿大量參數(shù)給被調(diào)用腳本。并且如果,訪問量很高的時(shí)候,會(huì)產(chǎn)生大量的進(jìn)程。如果使用到了外部資源,還要自己考慮競(jìng)爭。
1)只能在本機(jī)執(zhí)行
2)不能傳遞大量參數(shù)
3)訪問量高時(shí)會(huì)創(chuàng)建很多進(jìn)程
3. curl 擴(kuò)展
CURL是一個(gè)強(qiáng)大的HTTP命令行工具,可以模擬POST/GET等HTTP請(qǐng)求,然后得到和提取數(shù)據(jù),顯示在"標(biāo)準(zhǔn)輸出"(stdout)上面。
設(shè)置curl的超時(shí)時(shí)間 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); ?>
缺點(diǎn):如你問題中描述的一樣,由于使用CURL需要設(shè)置CUROPT_TIMEOUT為1(最小為1,郁悶)。也就是說,客戶端至少必須等待1秒鐘。
4. fscokopen()函數(shù)
fsockopen是最好的,缺點(diǎn)是需要自己拼接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); } ?>
注意:當(dāng)執(zhí)行過程中,客戶端連接斷開或連接超時(shí),都會(huì)有可能造成執(zhí)行不完整,因此需要加上
ignore_user_abort(true); // 忽略客戶端斷開 set_time_limit(0); // 設(shè)置執(zhí)行不超時(shí)
fsockopen支持socket編程,可以使用fsockopen實(shí)現(xiàn)郵件發(fā)送等socket程序等等,使用fcockopen需要自己手動(dòng)拼接出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); /*忽略執(zhí)行結(jié)果 while (!feof($fp)) { echo fgets($fp, 128); }*/ fclose($fp); }
所以總結(jié)來說,fscokopen()函數(shù)應(yīng)該可以滿足您的要求??梢試L試一下。
fscokopen的問題和popen 一樣,并發(fā)非常多時(shí)會(huì)產(chǎn)生很多子進(jìn)程,當(dāng)達(dá)到apache的連接限制數(shù)時(shí),就會(huì)掛掉,我問題已經(jīng)說了這種情況。
PHP 本身沒有多線程的東西,但可以曲線的辦法來造就出同樣的效果,比如多進(jìn)程的方式來達(dá)到異步調(diào)用,只限于命令模式。還有一種更簡單的方式,可用于 Web 程序中,那就是用fsockopen()、fputs() 來請(qǐng)求一個(gè) URL 而無需等待返回,如果你在那個(gè)被請(qǐng)求的頁面中做些事情就相當(dāng)于異步了?! ?/p>
關(guān)鍵代碼如下:
$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 發(fā)送完請(qǐng)求就不管了,用不著等待請(qǐng)求頁面的響應(yīng)數(shù)據(jù),利用這一點(diǎn)就可以在被請(qǐng)求的頁面 another_page.php 中異步的做些事情了。
比如,一個(gè)很切實(shí)的應(yīng)用,某個(gè) Blog 在每 Post 了一篇新日志后需要給所有它的訂閱者發(fā)個(gè)郵件通知。如果按照通常的方式就是:
日志寫完 -> 點(diǎn)提交按鈕 -> 日志插入到數(shù)據(jù)庫 -> 發(fā)送郵件通知 ->
告知撰寫者發(fā)布成功
那么作者在點(diǎn)提交按鈕到看到成功提示之間可能會(huì)等待很常時(shí)間,基本是在等郵件發(fā)送的過程,比如連接郵件服務(wù)異常、或器緩慢或是訂閱者太多。而實(shí)際上是不管郵件發(fā)送成功與否,保證日志保存成功基本可接受的,所以等待郵件發(fā)送的過程是很不經(jīng)濟(jì)的,這個(gè)過程可異步來執(zhí)行,并且郵件發(fā)送的結(jié)果不太關(guān)心或以日志形式記錄備查。
改進(jìn)后的流程就是:
日志寫完 -> 點(diǎn)提交按鈕 -> 日志插入到數(shù)據(jù)庫 --->
告知撰寫者發(fā)布成功
└ 發(fā)送郵件通知 -> [記下日志]
用個(gè)實(shí)際的程序來測(cè)試一下,有兩個(gè) php,分別是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 來模擬程序執(zhí)行使用時(shí)間。
write.php,執(zhí)行耗時(shí) 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"); #請(qǐng)求的資源 URL 一定要寫對(duì) fclose($fp); } echo time().'<br>'; echo 'call asyn_sendmail<br>'; asyn_sendmail(); echo time().'<br>'; ?>
sendmail.php,執(zhí)行耗時(shí) 10 秒
<?php //sendmail(); //sleep 10 seconds sleep(10); fopen('C:\'.time(),'w'); ?>
通過頁面訪問 write.php,頁面輸出:
1272472697 call asyn_sendmail 1272472698
并且在 C:\ 生成文件:
1272472708
從上面的結(jié)果可知 sendmail.php 花費(fèi)至少 10 秒,但不會(huì)阻塞到 write.php 的繼續(xù)往下執(zhí)行,表明這一過程是異步的。
關(guān)于PHP異步執(zhí)行的4種常用方式是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。