前言
默认情况下,我们在PHP里使用echo
等函数输出的内容,是不会马上发送给前端的,原因是有 buffer 的存在,buffer又分两处,一处是PHP本身的buffer,另一处是Nginx的buffer。只有当buffer满了之后,内容才会发送。
但有时候我们会希望输出的内容可以马上发送给前端,例如类似ChatGPT之类的应用,回答都是一个一个字的实时输出的,给用户良好的体验。
那么,怎么关闭 PHP 和 Nginx 的 buffer 呢?
环境
Nginx 1.19
PHP 7.4
解决方法
一、PHP的buffer
PHP里有两个函数可以关闭buffer缓冲,一个是ob_end_flush,一个是ob_end_clean,前者是输出缓冲区内容后关闭缓冲区,后者是销毁缓冲区内容直接关闭。
但即使我们关闭了 PHP 的缓冲区,每次输出完内容也还是要手动 flush 的,例如:
echo 'Hello World';
flush();
每次echo
完都要调用一次flush
函数,太麻烦了,此时我们可以使用ob_implicit_flush函数来解决这个问题。
二、Nginx的buffer
Nginx有两种方法关闭缓冲区,第一种是改Nginx的配置文件:
加上图中红色框的配置指令就可以。
这种改配置文件的方法影响范围会比较大,会导致所有的PHP请求都会关闭缓冲区,不太推荐。
第二种方法是在 PHP 里输出 HTTP 响应头,只要在响应头里加上一个X-Accel-Buffering: no
,Nginx看到此响应头就会放弃使用buffer缓冲。由于这种方法是通过代码来控制,所以影响范围我们可以自由操控,推荐使用。
PHP代码示例
function stream()
{
// 如果缓冲区没有开启,直接调用ob_end_clean()会报错的,要先判断缓冲区有没有开启
// 如果ob_get_contents()不是返回false,说明有开启缓冲区(ob_start())
$buf = ob_get_contents();
if ($buf !== false) {
// 输出header前,不能有任何输出内容,否则会报错,所以缓冲区里的内容要全部清空
ob_end_clean();
}
ob_implicit_flush(); // 每次输出后都自动flush,这样就不需要咱们手动flush了
// 输出header,让Nginx不要使用buffer
header('X-Accel-Buffering: no');
// 每隔一秒输出一个数字
for ($i = 0; $i < 10; $i++) {
echo "$i\n";
sleep(1);
}
}
stream();
在 PowerShell 命令行中访问此页面,可以看到数字会一个一个的实时显示出来:
为什么要在命令行中访问?
答:因为有部分前端程序也是有自己的缓冲区的,即使后端实时输出内容了,前端也不会马上显示出来,为了避免这种问题,使用命令行来访问就很适合了。