如何在使用者中斷時停止PHP程式的運行

來源:互聯網
上載者:User
當我們以WEB的方式運行PHP指令碼時,預設情況下,即使你關閉當前頁面,程式也會繼續執行,直接程式結束或逾時。如果我們想在使用者關閉頁面或點擊了停止頁面運行時就中斷程式,我們需要做些什麼呢?上周和小毅同學討論了這個問題,從而也引出了今天我們這篇文章。

我們知道HTTP協議是基於TCP/IP協議,對於一個PHP頁面的請求就是一個HTTP請求(假設我們是Apache伺服器),從而會建立TCP串連,當使用者插斷要求時,會給伺服器一個abort狀態。這個abort狀態就是今天我們要講的關鍵點。

在PHP中有一個函數與abort狀態有關:ignore_user_abort函數
ignore_user_abort() 函數設定與客戶機斷開時是否會終止指令碼的執行。它返回 user-abort 之前設定的布爾值。它的參數可選。如果設定為 true,則忽略與使用者的斷開,如果設定為 false,會導致指令碼停止運行。

PHP 不會檢測到使用者是否已中斷連線,直到嘗試向客戶機發送資訊為止。因此如果我們只是使用echo語句,可能無法如實的看到abort的效果,因為PHP在輸出時會有一個緩衝,如果要重新整理緩衝,則可以使用flush() 函數。

如下代碼t.php:

ignore_user_abort(TRUE);set_time_limit(50); while(1){echo$i++,"\r\n";flush();     $fp=fopen("data.txt",'a');fwrite($fp,$i." \r\n");fclose($fp);     sleep(1);}

在瀏覽器中執行這段代碼,過了大概兩秒後,關閉請求的頁面,50秒後,你會發現在data.txt檔案中寫入了至少50個數。這表示我們的中斷操作是無效的。
如果我們改一下,把第一句改為:ignore_user_abort(FALSE);,重複上面的操作,你會發現,唯寫入了極少的數字,這表示我們的中斷操作有效了。
現在通過ignore_user_abort函數,我們實現了使用者中斷就馬上停止程式的操作。這裡有一個問題,即我們需要不停的flush,通過flush函數來更新串連狀態,當狀態為abort時,程式根據ignore_user_abort的設定來判斷是否中斷程式。除此之外,我們也可以使用直接擷取串連狀態來check串連狀態,並對特定的狀態作出處理,如下代碼:

ignore_user_abort(FALSE);set_time_limit(50); while(1){     echo$i++,"\r\n";flush();      if(connection_status()!= CONNECTION_NORMAL){break;}     $fp=fopen("data.txt",'a');fwrite($fp,$i.":".connection_status()." \r\n");fclose($fp);     sleep(1);}

這裡的connection_status函數的作用是擷取串連的狀態,當串連的狀態非normal時,我們就中斷迴圈,從而也達到了中斷程式的操作。這個樣本與前面的樣本不同之處在於中斷操作是由我們自己控制,而不是通過flush操作直接exit。如果在使用者中斷後還有一些其它的操作,這種方式會更合適一些。當然,這裡的flush操作依舊不可少,我們還是需要通過這個函數做check操作。

ignore_user_abort函數和connection_status函數都實現了我們的目的,這兩個函數的實現有沒有關聯?我們在ext/standard/basic_functions.c檔案中找到這兩個函數的實現如下:

/* {{{ proto int connection_aborted(void) Returns true if client disconnected */PHP_FUNCTION(connection_aborted){    RETURN_LONG(PG(connection_status)& PHP_CONNECTION_ABORTED);}/* }}} */ /* {{{ proto int connection_status(void)Returns the connection status bitfield */PHP_FUNCTION(connection_status){    RETURN_LONG(PG(connection_status));}/* }}} */ /* {{{ proto int ignore_user_abort([string value])Set whether we want to ignore a user abort event or not */PHP_FUNCTION(ignore_user_abort){char*arg = NULL;int arg_len =0;int old_setting;     if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"|s",&arg,&arg_len)== FAILURE){return;}     old_setting = PG(ignore_user_abort);     if(arg){        zend_alter_ini_entry_ex("ignore_user_abort",sizeof("ignore_user_abort"), arg, arg_len, PHP_INI_USER,     PHP_INI_STAGE_RUNTIME,0 TSRMLS_CC);}     RETURN_LONG(old_setting);}/* }}} */

connection_status函數直接返回PG(connection_status)的值,

ignore_user_abort函數重新設定PG(ignore_user_abort)的值,

不管是因為緩衝滿自動調用或通過flush函數調用的flush操作,其最終都會根據串連狀態判斷是否執行php_handle_aborted_connection函數,如果是abort狀態,則執行。

其代碼如下:

/* {{{ php_handle_aborted_connection*/PHPAPI void php_handle_aborted_connection(void){    TSRMLS_FETCH();     PG(connection_status)= PHP_CONNECTION_ABORTED;    php_output_set_status(0 TSRMLS_CC);     if(!PG(ignore_user_abort)){        zend_bailout();}}/* }}} */

在PG(ignore_user_abort)為假時,即不忽略使用者的中斷行為時,如果調用了此函數,則使用zend_bailout函數跳出程式直接exit。

在預設情況下ignore_user_abort為0,即不忽略使用者的中斷行為。

如果你是ubuntu的預設apache環境下,可能上面的代碼會無效。這是由於此環境下的apache開啟了zip,在沒有達到預定的大小時,伺服器不會與用戶端通訊,從而也就無法擷取用戶端的狀態,即使使用了flush函數也是一樣。

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.