问题

开拓中常常会碰着这种情形:当用户触发某个要求后,须要PHP做一些处理,但是不须要用户等待处理完成,也便是要求须要快速相应并结束,但结束后须要PHP在运行一段韶光做一些扫尾的处理。

比如用户做某个操作后,须要发邮件,这里假设没有行列步队,而是直接通过smtp进行发送,由于发送邮件建立tcp连接很耗时,而用户浏览器真个要求一贯在等待做事端相应结束,给用户的体验是页面一贯在加载中,卡在那里了,以是可以考虑后端先正常结束相应,让用户“觉得”操作已经成功结束,然后PHP再连续运行一定韶光去发送邮件。

phpsleep服资源另类方法实现PHP后台运行 Java

办法

这个方法利用到的是HTTP的特性,先整理一下思路:

HTTP是无状态的HTTP是要求-应答模式HTTP是建立在TCP之上的浏览器在要求一个web资源时(本文指PHP文件)会等待Web做事器的相应(本文中指Apache)直到相应结束如果在等待的过程中用户点击了浏览器上的停滞按钮,浏览器会关掉TCP连接,也便是中止当前的HTTP的要求-应答过程,根据对TCP的理解,这个中止是向做事器端发送了一条TCP指令底层连接TCP断掉,当前未完成的相应输出不到浏览器上

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

web做事器相应http头中有一个头信息:Connection 会奉告浏览器连接的保持情形,一样平常情形下都是: Connection:keep-alive 并且还有其余一个头说了要保持多久:Keep-Alive:300。
那如果做事器的相应中说连接已经关闭(connection: close)了会发生什么呢?浏览器会停滞等待相应,(rfc 2616)

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

反应到浏览器上便是页面要求完了,但是php并没有实行完,也便是将连续实行(实行韶光受php的代码逻辑和php.ini设置的最大运行韶光限定),这就实现了浏览器及所要求的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后台实行的效果, #休眠的浸染只是演示先把前面的输出作完,不要急于立时实行下面的代码,安歇一下而已,也便是说下面的代码 #实行的时候前面的输出该当到达浏览器了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解析实行,并把实行的结果返回给 Apache,apache在返回给浏览器,但如果这些相应输出不到浏览器(浏览器端中止链接了),apache会关照PHP,PHP就会中止当前要求文件的实行。

也便是说如果一个要求的过程中用户关掉了浏览器,或者点停滞按钮的话,所要求的php代码可能就会只实行到一半,没有实行完,如果这个时候是在做一些数据库的写操作,数据就可能没有写完备。

在这种情形下如果PHP仍旧要连续实行,可以利用PHP的一些连接掌握函数来忽略客户端退出连接的情形:

ignore_user_abort,该函数表示客户端断掉后是否要中止PHP的实行。
默认是中止

换句话说,在要求的过程中浏览器是否非正常退出PHP是可以知道的,可能通过connection_status()来得到连接状态:

0 – 正常 1 – 中止 2 – PHP实行超时

这三个状态可以叠加,也便是可以有 3 – 中止+PHP实行超时

问题是我们在什么时候调用connection_status()来得到连接的状态呢,在一样平常的代码中调用,得到的都是0,但如果浏览器中止了,php的实行也中止了,在一样平常的代码中这个函数不能很好的看到预期的结果,PHP供应了一个hook:register_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>\公众; }