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()函数,减少服务器开销。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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