欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

如何在用户中断时停止PHP程序的运行

程序员文章站 2022-05-13 20:28:15
...
当我们以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函数也是一样。

相关标签: php php技巧