溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP異步執(zhí)行的4種常用方式是什么

發(fā)布時(shí)間:2020-12-07 10:19:59 來源:億速云 閱讀:192 作者:小新 欄目:編程語言

這篇文章將為大家詳細(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ò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(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)容。

AI