另类方式实现PHP后台运行

张沫 发布于 2010/10/15 14:10
阅读 3K+
收藏 2
PHP

本文摘自《草根》杂志第四期

 

最近在考虑项目为用户群发邮件时想到了这个方法,觉得还有用,所以记录下来(项目情况是:用户要根据实际情况给他的客户发邮件,他的客户的数量是成千上万,根据情况选好客户,写好邮件点发送后要等着所有这些邮件发完不太现实,所以考虑后台运行),这个方法使用到的是HTTP的特性,先整理一下思路:

1.HTTP是无状态的

2.HTTP是请求-应答模式

3.HTTP是建立在TCP之上的(TCP建立在IP之上,每次请求浏览器都会使用一个随机的端口与服务器上的web端口(如80)建立socket连接,浏览器的随机端口可以通过$_SERVER查看到)

4.浏览器在请求一个web资源时(本文指PHP文件)会等待Web服务器的响应(本文中指Apache)直到响应结束

5.如果在等待的过程中用户点击了浏览器上的停止按钮,浏览器会关掉TCP连接,也就是中止当前的HTTP的请求-应答过程,根据对TCP的理解,这个中止应该是向服务器端发送了一条TCP指令

6.底层连接TCP断掉,当前未完成的响应当然也就输出不到浏览器上了

上面几条都好理解,但第4点还有细节:

web服务器的响应http头中有一个头信息:Connection 会告知浏览器连接的保持情况,一般情况下都是:
Connection
keep-alive
并且还有另外一个头说了要保持多久:Keep-Alive300

那如果服务器的响应中说连接已经关闭(connection: close)了会发生什么呢?浏览器会停止等待响应,(rfc 2616

那现在如果用户请求的PHP输出HTTP头:Connection:close。并把这些头输出到浏览器,然后再继续执行后面的代码,会是什么效果呢?

反应到浏览器上就是页面请求完了,但是php并没有执行完,也就是将继续执行,这就实现了浏览器及所请求的php异步执行的效果

例子:

ob_end_clean();#清除之前的缓冲内容,这是必需的,如果之前的缓存不为空的话,里面可能有http头或者其它内容,导致后面的内容不能及时的输出

header("Connection: close");#告诉浏览器,连接关闭了,这样浏览器就不用等待服务器的响应

#可以发送200状态码,以这些请求是成功的,要不然可能浏览器会重试,特别是有代理的情况下

ob_start();#开发当前代码缓冲

 

//{{逻辑代码

 

echo "一些处理";

 

//逻辑代码}}

 

//下面输出http的一些头信息

$size=ob_get_length();

header("Content-Length: $size");

ob_end_flush();#输出当前缓冲

flush();#输出PHP缓冲

 

#休眠PHP,也就是当前PHP代码的执行停止,1秒钟后PHP被唤醒,

#PHP唤醒后,继续执行下面的代码,但这个时候上面代码的结果已经输出浏览器了,

#也就是浏览器从HTTP头中知道了服务端关闭了连接,浏览器将不在等待服务器的响应,

#反应给客户的就是页面不会显示处于加载状态中,换句话说用户可以关掉当前页面,或者关掉浏览器,

#PHP唤醒后继续执行下面的代码,这也就实现了PHP后台执行的效果,

#休眠的作用只是让php先把前面的输出作完,不要急于马上执行下面的代码,休息一下而已,也就是说下面的代码

#执行的时候前面的输出应该到达浏览器了

sleep(1);

echo '这里的输出用户看不到,后台运行的';

 

//下面代码的任何输出都不会输出给浏览器,因为http连接已经关了,

//所以下面的代码的执行属于后台运行的

 

set_time_limit(0);#不受时间限制

$f = fopen('1.txt','a+');

for($i=0;$i<1000;$i++){

       if (fwrite($f,$i."

 

") === FALSE) {

        echo "Cannot write to file ($filename)";

       }

}

fclose($f);

 

其它情况:这种做法是让PHP主动告诉浏览器结束对话,这个过程应该是很快的,PHP收到请求后马上发送http给浏览器,但有时候的情况是PHP要先做一些事情,然后在把连接断掉的http响应返回给浏览器,但如果这个时候出现了网络或者其它一些意外情况导致了浏览器关掉了或者失去与服务器了连接了,PHP的响应头输出到不了浏览器上,PHP还会继续执行吗?

 

与浏览器作请求-应答这个过程的是Web服务器,如果请求的是PHP或者其它服务端语言,根据服务器的配置(loadmodule addtype这些)这些资源的请求会转到对应的语言处理器上,如所有的.php访问都会由PHP解析执行,并把执行的结果返回给 Apacheapache在返回给浏览器,但如果这些响应输出不到浏览器,apache会通知PHPPHP就会中止当前请求文件的执行。

也就是说如果一个请求的过程中用户关掉了浏览器,或者点停止按钮的话,所请求的php代码可能就会只执行到一半,没有执行完,如果这个时候是在做一些数据库的写操作,数据就可能没有写完全。但也只是有输出的时候PHP才会收到客户端已经中止的通知,如果php没有任何的输出,就算浏览器关了,PHP代码也会完全执行完,那什么时候PHP会输出呢?通常PHP有一个输出缓冲,缓冲区满后就输出或者程序正常结束时也输出。

为什么有输出的时候PHP才知道浏览器是否是退出了,大概是因为Apache输出失败才反馈给PHP,如果没有输出,PHP就在一边默默的执行,Apache不通知它

在这种情况下如果PHP仍然要继续执行,可以使用PHP的一些连接控制函数来忽略客户端退出连接的情况:
ignore_user_abort
,该函数表示客户端断掉后是否要中止PHP的执行。默认是中止

换句话说,在请求的过程中浏览器是否非正常退出PHP是可以知道的,可能通过connection_status()来得到连接状态:
0 –
正常
1 –
中止
2 – PHP
执行超时
这三个状态可以叠加,也就是可以有 3 – 中止+PHP执行超时

问题是我们在什么时候调用connection_status()来得到连接的状态呢,在一般的代码中调用,得到的都是0,但如果浏览器中止了,php的执行也中止了,在一般的代码中这个函数不能很好的看到预期的结果,PHP提供了一个hookregister_shutdown_function该方法用于注册请求结束时的回调,不管请求是正常结束还是异常结束,只要PHP在执行这个回调是一定会调用到。可以在该方法中查看 connection_status()返回值,

              function shutdown(){

  $f = fopen('1.txt','a+');

  fwrite($f,connection_status());

}

 

register_shutdown_function('shutdown');

 

while(1){

       #如果注掉,用户点了停止,PHP也会执行到超时,文件1.txt中写入的是2 PHP执行超时

       #如果不注掉,用户点了停止,文件1.txt中写入的是1 - 用户中止

       echo ++$i."<br>";

}

加载中
返回顶部
顶部