詳解 PHP 非同步後台處理

藏色散人
發布: 2023-04-09 16:54:02
轉載
7938 人瀏覽過

詳解 PHP 非同步後台處理

PHP 非同步後台處理

#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):

登入後複製

實際上,我們伺服器在執行fsockopen 那段程式時,就不會再等20s之後才能返回給客戶端,

而是發出這個請求之後,也就是返回客戶端,銷毀進程,而把剩餘的工作交由其他進程慢慢做去,這就實現了php的非同步。

PHP 非同步執行的4種常用方式

客戶端與伺服器端是透過HTTP協定進行連線通訊,客戶端發起請求,伺服器端接收到請求後執行處理,並傳回處理結果。

有時伺服器需要執行很耗時的操作,如處理下載、訊息下發、郵件傳送等,這個操作的結果並不需要回傳給客戶端。

但因為php是同步執行的,所以客戶端需要等待服務處理完才可以進行下一步。

因此,對於耗時的操作適合異步執行,伺服器接收到請求後,處理完客戶端所需的資料就先傳回,剩餘耗時的操作再異步在伺服器後台執行。

PHP非同步執行的常用方式常見的有以下幾種,可以根據各自優缺點進行選擇:

1. ajax 請求

客戶端頁面採用AJAX技術請求伺服器

$.get("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秒

代码如下:

登入後複製

缺点:如你问题中描述的一样,由于使用CURL需要设置CUROPT_TIMEOUT为1(最小为1,郁闷)。也就是说,客户端至少必须等待1秒钟。

4. fscokopen()函数

fsockopen是最好的,缺点是需要自己拼接header部分。

'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)
\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()、fputs() 来请求一个 URL 而无需等待返回,如果你在那个被请求的页面中做些事情就相当于异步了。  

关键代码如下:

$fp=fsockopen('localhost',80,&$errno,&$errstr,5); if(!$fp){ echo "$errstr ($errno)
\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 秒

\n"; } sleep(1); fputs($fp,"GET /sendmail.php?param=1\r\n"); #请求的资源 URL 一定要写对 fclose($fp); } echo time().'
'; echo 'call asyn_sendmail
'; asyn_sendmail(); echo time().'
'; ?>
登入後複製

sendmail.php,执行耗时 10 秒

登入後複製

通过页面访问 write.php,页面输出:

1272472697 call asyn_sendmail 1272472698
登入後複製

并且在 C:\ 生成文件:

1272472708
登入後複製

从上面的结果可知 sendmail.php 花费至少 10 秒,但不会阻塞到 write.php 的继续往下执行,表明这一过程是异步的。

以上是詳解 PHP 非同步後台處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
php
來源:mimvp
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!