php異步學習(1)

1.爲啥PHP須要異步操做?javascript

通常來講PHP適用的場合是web頁面展現等耗時比較短的任務,若是對於比較花時間的操做如resize圖片、大數據導入、批量發送EDM、SMS等,就很容易出現操做超時狀況。你能夠說我能夠設置無限超時時間,等等你也要知道PHP有一個工做模式是fastcgi,PHP無限不超時,不表明fastcgi相應不超時……若是你還想說要fastcgi相應永不超時,我建議你應該跟大家的運維人員討論去……php

這個時候異步的操做就發揮他的做用了,因爲是非阻塞操做,操做會即時返回,而後在後臺再慢慢幹活。管你超時不超時的,我就沒有在當前的進程/線程下幹活。看吧是否是很美好,不過其實這也是個坑……html

2.PHP能夠實現異步操做嗎?java

答案是確定的,不過網上各類的純PHP實現得就有點彆扭了。socket模式、掛起進程模式、有的還直接fork進程。很好,各路神仙各顯神通。若是運維人員看到的話,必定會×××××大家的,不把web server跑死纔怪……node

那還有其餘更好的方法去實現這個異步操做的可能麼?有,如今咱們只有想怎麼開外掛了。查一下PECL主流的外掛方案有一堆的××MQ(消息隊列),其中有個用於任務分配的外掛進入了咱們的視線Gearman(其實這傢伙纔是角,我就不詳細介紹了,點鏈接看介紹)。python

3.爲啥選擇Gearman?linux

別的不說,就說他的client多,支持不少語言的client,你可使用大部分你喜歡的語言去寫worker。我我的是很煩語言之爭,你喜歡用神碼語言寫worker都隨你喜歡。有數據持久化支持(就是把隊列保存到數據庫介質中,那故障恢復也好作),有羣集支持(其實不少××MQ都有這些功能)。PECL上有擴展,也有純PHP實現擴展。反正這個Gearman也活了好久了,雜七雜八的問題都基本上解決了。web

4.基本思路數據庫

有了Gearman這外掛就簡單多了。就是向gearman發送一個任務,把執行的任務發出去,而後等待worker去調用PHP cli去運行咱們的php代碼。編程

我就寫了一下一個python的worker(別問我爲啥用python,1.我會python,2.linux下不用裝runtime),你能夠本身根據思路寫一個PHP的worker,不過嘛,本人是不太信得過PHP跑的worker。其餘語言飯能夠用java、node.js 或者其餘語言實現一個worker試試。對用Golang寫worker有興趣的朋友能夠找我。

phpasync_worker_py

很差意思,裏面是沒有註釋的。一個配置文件,一個py腳本。基本的功能也就是分析一下調用的參數,而後調用PHP Cli,就是那樣子而已。要讓py腳本跑起來請自行安裝python的gearman模塊。

 

而後到PHP的部分先上測試代碼:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
require_once 'PHPAsyncClient.php' ;
date_default_timezone_set( 'Asia/Shanghai' );
 
class AsyncTest {
 
     const
         LOG_FILE = '/debug.log' ;
 
     static public function run() {
         if (PHPAsyncClient::in_callback( __FILE__ )) {
             self::log( 'php Async callback' );
             PHPAsyncClient::parse();
             return ;
         }
         if (PHPAsyncClient::is_main( __FILE__ )) {
             self::log( 'main run' );
             $async_call = PHPAsyncClient::getInstance();
             $async_call ->AsyncCall( 'AsyncTest' , 'callback' , array (
                 'content' => 'Hello World!!!' ,
             ), array (
                 'class' => 'AsyncTest' ,
                 'method' => 'callback' ,
                 'params' => array (
                     'content' => 'Hello Callback!' ,
                 ),
             ), __FILE__ );
             return ;
         }
     }
 
     static public function callback( $args ) {
         self::log( 'AsyncTest callback run' );
         self::log( 'AsyncTest callback args:' .print_r( $args , true));
     }
 
     static public function log( $content ) {
         $fullname = dirname( __FILE__ ).self::LOG_FILE;
         $content = date ( '[Y-m-d H:i:s]' ). $content . "\n" ;
         file_put_contents ( $fullname , $content , FILE_APPEND);
     }
}
 
AsyncTest::run();

就3個靜態方法,一個是用於調試的log方法,其餘都是字面意思。這個例子是對這種調用方式有個初步印象。而後直接上PHP的全部源碼:

php_async.zip

而後應該會有不少人會說,win下安裝不了gearman……因此我把java版的gearman server也放上去吧。

 

java-gearman-service-0.6.6.zip

 

5.結論

通過以上配置犀牛同樣大的傢伙後(要裝一個Gearman,還要跑個Py腳本),咱們基本上就使PHP擁有了異步調用功能,固然其中還有一個狀態維護神馬的要本身去實現。因此發現,其實這個方案不咋樣,太複雜了。仍是使用一些web service的方式去作web callback會好點(問題是web callback同樣會超時……),這個請留意後續。

 

 

 

 

 

*******************************************

PHP實現異步調用方法研究與分享

做者: 字體:[ 增長  減少] 類型:轉載 時間:2011-10-27
 
瀏覽器和服務器之間只一種面向無鏈接的HTTP協議進行通信的,面向無鏈接的程序的特色是客戶端請求服務端,服務端根據請求輸出相應的程序,不能保持持久鏈接
 
 
這樣就出現了一個問題,一個客戶端的相應服務端可能執行1秒也有可能執行1分鐘,這樣瀏覽器就會一直處於等待狀態,若是程序執行緩慢,用戶可能就沒耐心關掉了瀏覽器。 

而有的時候咱們不須要關心程序執行的結果,沒有必要這樣浪費時間和耐心等待,那咱們就要想出辦法讓程序不收等待在後臺靜默執行。

好比如今有一個場景,給1000個用戶發送一封推薦郵件,用戶輸入或者導入郵件帳號了提交服務器執行發送。 
複製代碼代碼以下:

<?php 
$count=count($emailarr); 
for($i=0;$i<$count;$i++) 

  sendmail(.....);//發送郵件 

?> 


這段代碼用戶體驗極差,也沒法實際運用,首先發送這麼多郵件會產生服務器運行超時,其實漫長的用戶等待時間會讓用戶對系統產品懷疑和失去信心。可是用戶不須要等待到1000封郵件都發送完畢了才提交發送成功,咱們徹底能夠提交後臺後直接給用戶提示發送成功,而後讓後臺程序靜默依次發送。 
這個時候咱們就須要「異步執行」技術來執行代碼,異步執行的特色是後臺靜默執行,用戶無需等待代碼的執行結果,使用異步執行的好處: 
1.擺脫了應用程序對單個任務的依賴性 
2.提升了程序的執行效率 
3.提升了程序的擴展性 
4.在必定場景提升了用戶體驗 
5.由於PHP不支持多線程,使用異步調用的請求多個HTTP的方式達到了程序並行執行效果,可是注意的是請求的HTTP過多的話,會大大加大了系統的開銷 
PHP異步執行的經常使用方式: 
1.客戶端頁面採用AJAX技術請求服務器 
1. 最簡單的辦法,就是在返回給客戶端的HTML代碼中,嵌入AJAX調用,或者,嵌入一個img標籤,src指向要執行的耗時腳本。 
這種方法最簡單,也最快。服務器端不用作任何的調用。 
可是缺點是,通常來講Ajax都應該在onLoad之後觸發,也就是說,用戶點開頁面後,就關閉,那就不會觸發咱們的後臺腳本了。 
而使用img標籤的話,這種方式不能稱爲嚴格意義上的異步執行。用戶瀏覽器會長時間等待php腳本的執行完成,也就是用戶瀏覽器的狀態欄一直顯示還在load。 
固然,還可使用其餘的相似原理的方法,好比script標籤等等 

2.popen()函數 
resource popen ( string command, string mode ); 
//打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。 
因此能夠經過調用它,但忽略它的輸出。 
pclose(popen("/home/xinchen/backend.php &", 'r')); 
  這個方法避免了第一個方法的缺點,而且也很快。可是問題是,這種方法不能經過HTTP協議請求另外的一個WebService,只能執行本地的腳本文件。而且只能單向打開,沒法穿大量參數給被調用腳本。 
而且若是,訪問量很高的時候,會產生大量的進程。若是使用到了外部資源,還要本身考慮競爭。 

3.CURL擴展 
CURL是一個強大的HTTP命令行工具,能夠模擬POST/GET等HTTP請求,而後獲得和提取數據,顯示在"標準輸出"(stdout)上面 
複製代碼代碼以下:

$ch = curl_init(); 
$curl_opt = array(CURLOPT_URL, 'http://www.example.com/backend.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是一個很是強大的函數,支持socket編程,可使用fsockopen實現郵件發送等socket程序等等,使用fcockopen須要本身手動拼接出header部分 
官方文檔: http://cn.php.net/fsockopen/ 
複製代碼代碼以下:

$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30); 
if (!$fp) { 
echo "$errstr ($errno)<br />\n"; 
} else { 
$out = "GET /backend.php / HTTP/1.1\r\n"; 
$out .= "Host: www.example.com\r\n"; 
$out .= "Connection: Close\r\n\r\n"; 

fwrite($fp, $out); 
/*忽略執行結果 
while (!feof($fp)) { 
echo fgets($fp, 128); 
}*/ 
fclose($fp); 

因此,整體來看,最好用,最簡單的仍是第一種方法。 
最完美的應該是最後一種,可是比較複雜 
若是有更好的辦法,歡迎交流。
 
 
*******************************************************
1.使用Ajax 與 img 標記 

原理,服務器返回的html中插入Ajax 代碼或 img 標記,img的src爲須要執行的程序。 

優勢:實現簡單,服務端無需執行任何調用 

缺點:在執行期間,瀏覽器會一直處於loading狀態,所以這種方法並不算真正的異步調用。 
複製代碼代碼以下:

$.get("doRequest.php", { name: "fdipzone"} ); 

複製代碼代碼以下:

<img src="doRequest.php?name=fdipzone"> 

2.使用popen 

使用popen執行命令,語法: 
複製代碼代碼以下:

// popen — 打開進程文件指針 
resource popen ( string $command , string $mode ) 

複製代碼代碼以下:

pclose(popen('php /home/fdipzone/doRequest.php &', 'r')); 

優勢:執行速度快 

缺點:1.只能在本機執行 

2.不能傳遞大量參數 

3.訪問量高時會建立不少進程。 

3.使用curl 

設置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); 
?> 

4.使用fsockopen 

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); // 設置執行不超時 
 
 
 
 
 
 
*************************
PHP異步調用socket 
複製代碼代碼以下:

<? 
$host = "www.aaa.com"; 
$path = "/Report.php?ReportID=1"; 
$cookie = Session_id(); 
$fp = fsockopen($host, 80, $errno, $errstr, 30); 
if (!$fp) { 
print "$errstr ($errno)<br />\n"; 
exit; 

$out = "GET ".$path." HTTP/1.1\r\n"; 
$out .= "Host: ".$host."\r\n"; 
$out .= "Connection: Close\r\n"; 
$out .= "Cookie: ".$cookie."\r\n\r\n"; 
fwrite($fp, $out); //將請求寫入socket 
//也能夠選擇獲取server端的響應 
/*while (!feof($fp)) { 
echo fgets($fp, 128); 
}*/ 
//若是不等待server端響應直接關閉socket便可 
fclose($fp); 
?> 
 
 
 
**********************

php 異步調用方法

客戶端與服務器端是經過HTTP協議進行鏈接通信,客戶端發起請求,服務器端接收到請求後執行處理,並返回處理結果。

有時服務器須要執行很耗時的操做,這個操做的結果並不須要返回給客戶端。但由於php是同步執行的,因此客戶端須要等待服務處理完才能夠進行下一步。

 

所以對於耗時的操做適合異步執行,服務器接收到請求後,處理完客戶端須要的數據就返回,再異步在服務器執行耗時的操做。

 

1.使用Ajax 與 img 標記

原理,服務器返回的html中插入Ajax 代碼或 img 標記,img的src爲須要執行的程序。

優勢:實現簡單,服務端無需執行任何調用

缺點:在執行期間,瀏覽器會一直處於loading狀態,所以這種方法並不算真正的異步調用。

 

[javascript]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. $.get("doRequest.php", { name: "fdipzone"} );  
[html]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. <img src="doRequest.php?name=fdipzone">  

 

2.使用popen

使用popen執行命令,語法:

 

[php]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. // popen — 打開進程文件指針   
  2. resource popen ( string $command , string $mode )  
[php]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. pclose(popen('php /home/fdipzone/doRequest.php &', 'r'));  
優勢:執行速度快

 

缺點:1.只能在本機執行

           2.不能傳遞大量參數

           3.訪問量高時會建立不少進程。

3.使用curl

設置curl的超時時間 CURLOPT_TIMEOUT 爲1 (最小爲1),所以客戶端須要等待1秒

 

[php]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. <?php  
  2. $ch = curl_init();  
  3. $curl_opt = array(  
  4.     CURLOPT_URL, 'http://www.example.com/doRequest.php'  
  5.     CURLOPT_RETURNTRANSFER,1,  
  6.     CURLOPT_TIMEOUT,1  
  7. );  
  8. curl_setopt_array($ch, $curl_opt);  
  9. curl_exec($ch);  
  10. curl_close($ch);  
  11. ?>  

4.使用fsockopen

fsockopen是最好的,缺點是須要本身拼接header部分。

 

[php]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. <?php  
  2.   
  3. $url = 'http://www.example.com/doRequest.php';  
  4. $param = array(  
  5.     'name'=>'fdipzone',  
  6.     'gender'=>'male',  
  7.     'age'=>30  
  8. );  
  9.   
  10. doRequest($url, $param);  
  11.   
  12. function doRequest($url, $param=array()){  
  13.   
  14.     $urlinfo = parse_url($url);  
  15.   
  16.     $host = $urlinfo['host'];  
  17.     $path = $urlinfo['path'];  
  18.     $query = isset($param)? http_build_query($param) : '';  
  19.   
  20.     $port = 80;  
  21.     $errno = 0;  
  22.     $errstr = '';  
  23.     $timeout = 10;  
  24.   
  25.     $fp = fsockopen($host, $port, $errno, $errstr, $timeout);  
  26.   
  27.     $out = "POST ".$path." HTTP/1.1\r\n";  
  28.     $out .= "host:".$host."\r\n";  
  29.     $out .= "content-length:".strlen($query)."\r\n";  
  30.     $out .= "content-type:application/x-www-form-urlencoded\r\n";  
  31.     $out .= "connection:close\r\n\r\n";  
  32.     $out .= $query;  
  33.   
  34.     fputs($fp, $out);  
  35.     fclose($fp);  
  36. }  
  37.   
  38. ?>  
注意:當執行過程當中,客戶端鏈接斷開或鏈接超時,都會有可能形成執行不完整,所以須要加上

 

 

[php]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. ignore_user_abort(true); // 忽略客戶端斷開  
  2. set_time_limit(0);       // 設置執行不超時  

 

Tips:關於fsockopen的介紹與用法能夠參考我以前寫的《php 利用fsockopen GET/POST 提交表單及上傳文件》《PHP HTTP請求類,支持GET,POST,Multipart/form-data》

 

 

 

 

**************************************

深刻PHP異步執行的詳解

做者: 字體:[ 增長  減少] 類型:轉載 時間:2013-06-03
 
本篇文章是對PHP的異步執行進行了詳細的分析介紹,須要的朋友參考下
 
 
Web服務器執行一個PHP腳本,有時耗時很長才能返回執行結果,後面的腳本須要等待很長一段時間才能繼續執行。若是想實現只簡單觸發耗時腳本的執行而不等待執行結果就直接執行下一步操做,能夠經過fscokopen函數來實現。
PHP支持socket編程,fscokopen函數返回一個到遠程主機鏈接的句柄,能夠像使用fopen返回的句柄同樣,對它進行fwrite、fgets、fread等操做。使用fsockopen鏈接到本地服務器,觸發腳本執行,而後當即返回,不等待腳本執行完成,便可實現異步執行PHP的效果。
示例代碼以下:
複製代碼代碼以下:

<?
function triggerRequest($url, $post_data = array(), $cookie = array()){
        $method = "GET";  //經過POST或者GET傳遞一些參數給要觸發的腳本
        $url_array = parse_url($url); //獲取URL信息
        $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_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;
}   

這樣就能夠經過fsockopen()函數來觸發一個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]);//註冊腳本退出時執行的函數
相關文章
相關標籤/搜索