另类方式实现PHP后台运行

问题

开发中经常会遇到这种情况:当用户触发某个请求后,需要PHP做一些处理,但是不需要用户等待处理完成,也就是请求需要快速响应并结束,但结束后需要PHP在运行一段时间做一些收尾的处理。

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

办法

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

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

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

web服务器响应http头中有一个头信息:Connection 会告知浏览器连接的保持情况,一般情况下都是: 

1. Connection:keep-alive
2. 并且还有另外一个头说了要保持多久: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." "; 
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容