后台进程,当然是用合适的语言来做合适的事情,比如C/C++, Java, 当然PHP 也可以在linux上以CLI方式来实现Daemon Process;
这里分享的怪咖实现方法是,在运行于apache module 或者cgi模式下的php代码来实现后台运行的目的,也就是说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‐Alive:300。
那如果服务器的响应中说连接已经关闭(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 解析执行,并把执行的结果返回给 Apache,Apache 在返回给浏览器.
但如果这些响应输出不到浏览器, Apache 会通知 PHP,PHP 就会中止当前请求文件的执行。
也就是说如果一个请求的过程中用户关掉了浏览器,或者点停止按钮的话,所请求的 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 提供了一个 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>";
}