最近遇到一个小难题,用$.post、$.get、$.ajax请求PHP服务器时,总是无法异步返回数据。
经实验,得到这样的结论:
- 不同浏览器,请求不同域名-不阻塞:无需实验
 - 不同浏览器,请求同域名-不阻塞:
session_id()返回不同 - 同一浏览器,请求不同域名-不阻塞:
session_id返回不同 - 同一浏览器,请求同域名-阻塞:
session_id()返回相同 
多轮调试才发现问题所在:
1 关闭XDEBUG
很重要!
XDEBUG是实时调试,调试的时候会HOLD住FPM,保证一个线程在工作,避免数据污染。
典型的测试方法是,用XDEBUG调试时,打开另外一个浏览器,访问站点,这个时候是访问不了的。
这个对并行响应有重大影响,也就是说,即使前端发了多个请求,也被XDEBUG控制住,同时只能响应一个。
另外,因为XDEBUG依赖SESSION,所以即使你用了session_write_close(),关闭session锁(下面会讲到)。
XDEBUG还是会自动打开。
2 SESSION锁
用 session_write_close() 关闭SESSION的写锁,这适合SESSION保存为File的情况。
如果SESSION保存在Redis,则不需要。
3 清除输出缓冲区
使用session_write_close()可能无法立即关闭SESSION锁,所以在这个方法之前加上:ob_end_flush()。
让 session_write_close() 马上生效。
4 示例
有如下一个范例,当点击【提交】按钮时,前端会给后台服务器发送两种请求。
一种是get请求,每隔1秒请求一次。
一种是post请求,最开始的时候发送一次,然后等待相应结束。
HTML代码:
<form>
<input type="submit" value="提交" />
</form>
<script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
    $('form').on('submit', function(e) {
        e.preventDefault();
        // 每隔一秒请求一次服务器
        var id = setInterval(function() {
            $.get(
                'save.php?action=get',
                {},
                function(data) {
                    console.log(data);
                },
                'json'
            );
        }, 1000);
        $.post(
            'save.php?action=post',
            {},
            function(data) {
                console.log(data);
                // 停止定时循环
                clearInterval(id);
            },
            'json'
        );
    });
</script>
PHP服务器代码:
<?php
session_start();
$action = $_GET['action'];
if ($action == 'post') {
   
   $_SESSION['time'] = 0;
   session_write_close();
    while ($_SESSION['time'] < 5) {
      session_start();
      $_SESSION['time'] = $_SESSION['time'] + 1;
      // 将SESSION数据写入文件中,并关闭写锁
      session_write_close();
      // sleep()模拟花费时间较长的程序,这样在关闭写锁之后,
      // 服务器就能够相应别的请求,如下的$action=get,
      sleep(1);
    }
   
    echo json_encode([session_id() => $_SESSION['time']]);
    exit();
}
if ($action == 'get') {
    echo json_encode([session_id() => $_SESSION['time']]);
    exit();
}
参考资料:
- Session lock. session_write_close() doesn’t work
 - PHP Session Locks – How to Prevent Blocking Requests
 - asp.net/asp网站浏览器打开一个长时间运行的页面同时打开其他页面为什么被挂起
 - jquery同时发送两个异步ajax请求,但数据却是按同步顺序返回
 - AJAX 跨域请求 – JSONP获取JSON数据(PHP版)
 - PHP & Sessions: Is there any way to disable PHP session locking?
 - PHP Session Locking: How To Prevent Sessions Blocking in PHP requests
 
