PHP提前输出响应及注意问题

一、问题

浏览器和服务器之间是通过HTTP进行通信的,浏览器发送请求给服务器,服务器处理完请求后,发送响应结果给浏览器,浏览器展示给用户。如果服务器处理请求时间比较长,那么浏览器就需要等待服务器的处理结果。

但是,有时候,浏览器不需要等待服务器的处理结果,只要发送的请求已经被服务器接收到。所以,这种情况下,浏览器希望服务器接收到请求立即返回一个响应,比如字符串'success'。这样浏览器可以继续执行后续代码。

在PHP中,很容易做到。如果服务器使用的是nginx+fpm,可以使用下面的代码:

echo 'success';
fastcgi_finish_request();

// 执行耗时代码.....

如果服务器使用的是apache,可以使用下面的代码:

ob_end_flush();
ob_start();
echo 'success';

header("Content-Type: text/html;charset=utf-8");
header("Connection: close");
header('Content-Length: '. ob_get_length());

ob_flush();
flush();

// 执行耗时代码.....

二、测试

一个静态页面:index.html,代码如下:

<html>
<head><title>测试PHP提前输出响应</title></head>
<body>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>

function getCurTime(d) {
  var time = d.getFullYear() + "-" +(d.getMonth()+1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
  return time;
}

function getOther(url) {
  $.ajax({
    url: url,
    success:function(data){
      console.log(data);
      console.log(getCurTime(new Date()));
    }
  });
}

console.log(getCurTime(new Date()));
$.ajax({
  url: "http://www.test.com/test/1.php",
  success:function(data){
    console.log(data);
    console.log(getCurTime(new Date()));
    getOther("http://www.test.com/test/2.php");
  }
});

</script>
</body>
</html>

浏览器首先通过ajax请求a.php,该php文件执行是比较耗时的,要等到a.php的响应后继续请求b.php文件。如果a.php不提前输出响应,ajax必须等待响应后才能去请求b.php。

上面代码,会在每次请求发送开始和返回时打印出时间。

两个PHP文件:1.php 、 2.php
a.php文件内容:

<?php
echo 'success';

sleep(5);
?>

上面代码使用sleep函数模拟耗时任务,耗时5秒。

b.php文件内容:

<?php
echo 'this is b.php';
?>

浏览器执行index.html,查看console日志如下:

console日志

发现第二次请求时间比第一次请求时间迟5秒,正好是a.php文件的执行时间,说明浏览器需要等待a.php文件执行完才能去请求b.php文件。

下面修改a.php文件内容,提前输出响应,代码如下:

<?php
if(!function_exists('fastcgi_finish_request')) {
  ob_end_flush();
  ob_start();
}
echo 'success';

if(!function_exists('fastcgi_finish_request')) {
  header("Content-Type: text/html;charset=utf-8");
  header("Connection: close");
  header('Content-Length: '. ob_get_length());
  ob_flush();
  flush();
} else {
  fastcgi_finish_request();
}

sleep(5);
?>

浏览器执行index.html,查看console日志如下:

console日志

两次请求的时间都一样,表示a.php已经提前输出响应了。

三、session问题

开发web应用程序不可避免的要用到session,修改一下a.php文件内容如下:

<?php
session_start();
$_SESSION['uname'] = 'jianshu';

if(!function_exists('fastcgi_finish_request')) {
  ob_end_flush();
  ob_start();
}
echo 'success';

if(!function_exists('fastcgi_finish_request')) {
  header("Content-Type: text/html;charset=utf-8");
  header("Connection: close");
  header('Content-Length: '. ob_get_length());
  ob_flush();
  flush();
} else {
  fastcgi_finish_request();
}

sleep(5);
?>

修改b.php文件内容如下:

<?php
  session_start();
  echo $_SESSION['uname'];
  echo 'this is b.php';
?>

浏览器执行index.html,查看console日志如下:

console日志

发现第一次和第二次请求时间一样,说明第一次请求已经提前输出响应了,但是第二次请求的响应时间却比请求发送时间迟5秒。第二次请求的是b.php文件,b.php文件内容只有几行代码,为什么会执行5秒才返回。

原因是,session是有锁的。为防止并发的写session数据,PHP自带的的文件保存session数据是加了一个互斥锁。程序执行session_start(),此时当前程序就开始持有锁。程序结束,此时程序自动释放Session的锁。

浏览器第一次请求a.php文件,对session文件进行加锁,虽然提前输出响应,但程序还在执行,session文件的锁还未释放。所以,第二次请求b.php文件时,由于b.php文件的开始就要打开session文件,但session文件的锁还未释放,需要等待a.php执行完,也即要等待5秒。

一般session文件跟SESSION_ID有关,每个SESSION_ID会创建一个session文件,下面可以看到两次请求带过去的SESSION_ID是一样的。

a.php文件请求SESSID
b.php文件请求SESSID

如何解决session文件加锁的问题,可以使用session_write_close函数,该函数的作用就是数据写入session文件并结束会话。
修改a.php文件,在使用session代码后面加上session_write_close函数。

<?php
session_start();
$_SESSION['uname'] = 'jianshu';
session_write_close();
............

浏览器执行index.html,查看console日志如下:

console日志

发现没有等待存在了。实际开发中,使用完session,可以加上session_write_close()函数,减少服务器开销。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,018评论 11 349
  • Php:脚本语言,网站建设,服务器端运行 PHP定义:一种服务器端的HTML脚本/编程语言,是一种简单的、面向对象...
    廖马儿阅读 2,084评论 2 38
  • 1. 网络基础TCP/IP HTTP基于TCP/IP协议族,HTTP属于它内部的一个子集。 把互联网相关联的协议集...
    yozosann阅读 3,405评论 0 20
  • 昨日读了一下午乾卦,越思越觉其妙其深,始感《易经》不愧诸经第一经。 乾卦为君子之卦,也为如何成为君子指名了方向,同...
    张骞涛阅读 530评论 0 0