38

我编写了一个简单的中继脚本,它连接到网络摄像头并从套接字读取数据,并使用打印功能输出这些数据。数据是已设置边界的 MJPG 数据。我只是输出读取的数据。

问题是 PHP 似乎正在缓冲这些数据。当我将相机设置为 1 FPS 时,提要会冻结 7-8 秒,然后快速显示 8 帧。如果我将分辨率设置为很大,相机会以每秒或多或少 1 帧的速度移动。我假设当时正在发生一些缓冲(因为大尺寸会快速填充缓冲区,而小尺寸不会),我不知道如何禁用这种缓冲。有谁知道怎么做?

代码:

ignore_user_abort(false);

$boundary = "myboundary";

//Set this so PHP doesn't timeout during a long stream
set_time_limit(0);

$socketConn = @fsockopen ("192.168.1.6", 1989, $errno, $errstr, 2);
if (!$socketConn)
exit();
stream_set_timeout($socketConn, 10);
fputs ($socketConn, "GET /mjpeg HTTP/1.0\r\n\r\n");

//Setup Header Information
header("Cache-Control: no-cache");
header("Cache-Control: private");
header("Pragma: no-cache");
header("Content-type: multipart/x-mixed-replace; boundary=$boundary");

@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
ob_end_flush();
ob_implicit_flush(1);

stream_set_blocking($f2, false);

//Send data to client
while (connection_status() == CONNECTION_NORMAL)
{
    $chunk = fread($socketConn, 128);
    print $chunk;   
}

fclose($socketConn);
4

6 回答 6

67

tl;博士版

做两件事:

  1. 禁用用户空间输出缓冲区,或者...

    • 在全球范围内,无论是...

      • output_buffering在你的 php.ini 中关闭,或者
      • output_buffering使用在您的 Apache 配置中关闭

        php_flag "output_buffering" Off
        
    • 或者只是你关心的脚本,通过......

      • 调用ob_end_flush(),或
      • 打电话ob_end_clean()
  2. 此外,尽可能多地禁用服务器级输出缓冲区,方法是:

    • ob_implicit_flush()在脚本开头调用,或
    • 在将输出添加到响应正文的flush()每个语句或其他语句之后调用echo

更长的版本

令人困惑的是,有两层缓冲可能是相关的,而 PHP 文档在区分两者方面做得很差。

输出缓冲区

第一层通常被 PHP 文档称为“输出缓冲区”。这一缓冲层仅影响到HTTP 响应正文的输出,而不影响标头。您可以使用 开启输出缓冲,使用或ob_start()关闭它。您还可以使用php.ini 中的选项让所有脚本自动从输出缓冲开始。ob_end_flush()ob_end_clean()output_buffering

对于php.ini的生产版本,此选项的默认值为4096,这意味着输出的前 4096 个字节将被缓冲在输出缓冲区中,此时它将被刷新并关闭输出缓冲。

您可以通过在 php.ini 文件中设置output_buffering为(或使用Off

php_flag "output_buffering" Off

在您的 Apache 配置中,如果您使用的是 Apache)。ob_end_clean()或者,您可以通过调用或ob_end_flush()在脚本开头禁用它来为单个脚本禁用它。

写缓冲区和网络服务器缓冲区

除了输出缓冲区之外,还有 PHP 手册中称为“写入缓冲区”的内容,以及您的 Web 服务器拥有的任何缓冲系统。如果您通过 Apache 使用 PHPmod_php并且不使用mod_gzip,则可以调用flush()以刷新这些;对于其他后端,它可能也可以工作,尽管手册对提供保证很谨慎:

描述

void flush ( void )

刷新 PHP 的写入缓冲区以及 PHP 正在使用的任何后端(CGI、Web 服务器等)。这试图将当前输出一直推送到浏览器,但有一些警告。

flush()可能无法覆盖 Web 服务器的缓冲方案,并且它对浏览器中的任何客户端缓冲都没有影响。它也不会影响 PHP 的用户空间输出缓冲机制。这意味着如果您正在使用ob_flush()flush()来刷新 ob 输出缓冲区,则必须调用它们。

还有几种方法可以让 PHP 在flush()您每次执行任何操作时自动调用echo(或执行任何其他将输出回显到响应正文的操作)。

首先是打电话ob_implicit_flush()。请注意,此函数的名称具有欺骗性;鉴于它的ob_前缀,任何合理的人都会期望它会影响“输出缓冲区”,就像 等一样ob_startob_flush然而,事实并非如此;ob_implicit_flush()和 一样flush(),影响服务器级别的输出缓冲区,并且不会以任何方式与其他ob_函数控制的输出缓冲区进行交互。

第二个是通过在 php.ini 中设置implicit_flush标志来全局启用隐式刷新。On这相当于ob_implicit_flush()在每个脚本的开头调用。请注意,手册建议不要这样做,隐晦地引用“严重的性能影响”,其中一些我在这个切线相关的答案中进行了探索。

于 2014-06-08T16:31:15.377 回答
20

flush()您可以在每次读取操作后调用,而不是禁用输出缓冲。这避免了弄乱服务器配置并使您的脚本更具可移植性。

于 2012-01-16T15:46:44.620 回答
14

输出缓冲可以分层,我曾经遇到过早期代码制作多个级别的情况。这将清除它们。

while (ob_get_level()) ob_end_clean(); 
// or ob_end_flush() if you want the contents of the buffer.
于 2016-10-26T08:38:47.190 回答
3

我们可以在 .htaccess 文件中提供以下代码,用于在 PHP 中禁用输出缓冲

php_flag "output_buffering" off
于 2013-06-05T12:29:24.140 回答
0

我知道这个问题有点老了,但是回到这个问题,您可以在脚本到脚本的基础上关闭输出缓冲,如下所示:

if (ob_get_level())
   ob_end_clean();

这应该关闭它后面的任何脚本的所有输出缓冲。

于 2014-04-30T15:41:35.780 回答
0

对于 Windows IIS 服务器,上述解决方案都不起作用,因为 IIS 管理自己的缓冲区,也应该禁用它。

只需web.config使用 PHP 脚本将以下内容添加到您的文件夹即可禁用缓冲。在下面的示例中使用 PHP v7.3.7,但您可以将名称替换为另一个版本。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <clear />
            <add name="php-7.3.7" path="*.php" verb="GET,HEAD,POST" modules="FastCgiModule" scriptProcessor="C:\Program Files\PHP\v7.3\php-cgi.exe" resourceType="Either" requireAccess="Script" responseBufferLimit="0" />
        </handlers>
    </system.webServer>
</configuration>
于 2019-12-10T23:50:34.420 回答