­

php提前響應請求繼續執行程式碼(偽非同步)

  • 2019 年 12 月 19 日
  • 筆記

在很多業務需求中,我們都可能需要先讓php給瀏覽器輸出,然後在後台慢慢處理其他不用輸出耗時的業務.

那麼,php該怎麼實現這個功能呢?

ignore_user_abort(true);

首先,我們先來了解下ignore_user_abort(true);這個函數

這個函數可以忽略客戶機的斷開,繼續執行php程式碼

那到底這個用來幹啥的呢?例如:

//當用戶A用瀏覽器請求下單邏輯  //由於後台邏輯非常多,需要處理20秒  //用戶A等了10秒等不下去,關閉了網頁  //默認情況下,用戶關閉了網頁,php進程則會直接終止,相當於執行了一半邏輯之後,停止了  //用戶後面發現,自己已經有了這個訂單數據,卻沒有訂單詳情(執行一半沒來得及插入)

這個時候,ignore_user_abort就有用了,當忽略客戶機斷開後,php會一直執行,直到異常終止或已完成操作

set_time_limit(0);

在上面講到,如果啟用ignore_user_abort 則會讓php一直執行,直到異常終止,而在php常規web模式下,默認有個執行超時時間(30秒),當執行到30秒時,會直接終止該php進程,可使用set_time_limit(0),設置為用不超時,這樣的話,客戶端就算斷開,就算超過30秒,php進程也會一直執行下去,直到執行完成

實時輸出

在我之前的一篇講buffer緩衝區的文章中,有講到過瀏覽器實時輸出,刷新緩衝區可以讓php+web伺服器的輸出變成實時輸出,不再需要等待腳本結束才顯示內容.然而,apache和nginx的實現方式也有所不同

<?php  //apache方法,需要關閉apache緩衝區  for($i=0;$i<1000;$i++){  	echo $i;        ob_flush();//刷新PHP自身緩衝區        flush();//刷新(特指apache)web伺服器的緩衝區,輸出數據        sleep(1);  }    //nginx緩衝區  ob_end_clean();  ob_implicit_flush();  header('X-Accel-Buffering: no'); // 關鍵是加了這一行。  for($i=0;$i<1000;$i++){      echo $i;      sleep(1);  }

用以上方法,就可以使php的echo,實時輸出到瀏覽器中

偽結束響應

在認識到上面3種概念之後,我們就要開始實現這個功能了 偽結束響應原理是:

先讓php提前輸出"已結束響應"程式碼(其實還沒有結束,還可以繼續echo輸出)

然後讓用戶自行關閉窗口,通過set_time_limit和ignore_user_abort函數實現php程式碼還在後台運行,如以下例子:

<?php  //apache伺服器  set_time_limit(0);  ignore_user_abort(true);  //巴拉巴拉這裡處理了一些事情  echo "完成請求,3秒自動關閉頁面(一段js自動關閉頁面)";  ob_flush();//刷新PHP自身緩衝區  flush();//刷新(特指apache)web伺服器的緩衝區,輸出數據  //這裡還在巴拉巴拉處理事情  $i=0;  while(1){  //注意,死循環非常危險,會造成該web進程一直在處理,不會退出,永久佔用一個進程,而且管理該進程非常麻煩,建議加個判斷啥的      file_put_contents('test.txt',$i);      $i++;      sleep(1);  }    //nginx伺服器  set_time_limit(0);  ignore_user_abort(true);  //巴拉巴拉這裡處理了一些事情  ob_end_clean();  ob_implicit_flush();  header('X-Accel-Buffering: no'); // 關鍵是加了這一行。  echo "完成請求,3秒自動關閉頁面(一段js自動關閉頁面)";  //這裡還在巴拉巴拉處理事情  $i=0;  while($i<100){      //注意,死循環非常危險,會造成該web進程一直在處理,不會退出,永久佔用一個進程,而且管理該進程非常麻煩,建議加個判斷啥的      file_put_contents('test.txt',$i);      $i++;      sleep(1);  }

提前結束響應

在php-fpm中,有個函數fastcgi_finish_request可使得web伺服器提前中斷http響應:

<?php  //php-fpm模式下  set_time_limit(0);  ignore_user_abort(true);  //巴拉巴拉這裡處理了一些事情  echo "完成請求,3秒自動關閉頁面(一段js自動關閉頁面)";  fastcgi_finish_request();//真正的結束響應,後面的echo將不起作用    //這裡還在巴拉巴拉處理事情  $i=0;  while($i<100){      //注意,死循環非常危險,會造成該web進程一直在處理,不會退出,永久佔用一個進程,而且管理該進程非常麻煩,建議加個判斷啥的      file_put_contents('test.txt',$i);      $i++;      sleep(1);  }

在非fpm模式下,該怎麼提前中斷呢?

<?php  //非php-fpm  一般是apache  set_time_limit(0);  ignore_user_abort(true);  ob_end_flush();  ob_start();  //巴拉巴拉這裡處理了一些事情  echo '完成請求,3秒自動關閉頁面(一段js自動關閉頁面)';  header("Content-Type: text/html;charset=utf-8");  header("Connection: close");//告訴瀏覽器不需要保持長連接  header('Content-Length: '. ob_get_length());//告訴瀏覽器本次響應的數據大小只有上面的echo那麼多  ob_flush();  flush();  //header不會經過buffer,直接輸出到瀏覽器,瀏覽器接收到之後,直接主動關閉連接    //這裡還在巴拉巴拉處理事情  $i=0;  while($i<100){      //注意,死循環非常危險,會造成該web進程一直在處理,不會退出,永久佔用一個進程,而且管理該進程非常麻煩,建議加個判斷啥的      file_put_contents('test.txt',$i);      $i++;      sleep(1);  }

本文為仙士可原創文章,轉載無需和我聯繫,但請註明來自仙士可部落格www.php20.cn