我见过很多关于如何有效使用PHP下载文件的问题,而不是允许直接的HTTP请求(保证文件安全,跟踪下载等)。
答案几乎总是PHP readfile()。
但是,虽然它在使用大文件进行测试时效果很好,但是当它在拥有数百个用户的实时网站上时,下载开始挂起并且PHP内存限制已经用尽。
那么readfile()
如何工作导致内存在流量高时爆炸如此糟糕呢?我以为它应该通过直接写入输出缓冲区绕过大量使用PHP内存?
编辑:(澄清一下,我正在寻找“为什么”,而不是“我能做什么”。我认为Apache的mod_xsendfile是绕过的最佳方式)
答案 0 :(得分:5)
Description
int readfile ( string $filename [, bool $use_include_path = false [, resource $context ]] )
Reads a file and writes it to the output buffer*.
PHP必须读取文件并将其写入输出缓冲区。 因此,对于300Mb文件,无论您编写的实现(由许多小段或1个大块),PHP最终都必须读取300Mb的文件。
如果多个用户必须下载该文件,则会出现问题。 (在一台服务器中,托管服务提供商将限制给予每个托管用户的内存。使用这种有限的内存,使用缓冲区不是一个好主意。)
我认为使用直接链接下载文件对于大文件来说是一种更好的方法。
答案 1 :(得分:2)
如果你有输出缓冲,那么在调用readfile()之前使用ob_end_flush()
header(...);
ob_end_flush();
@readfile($file);
答案 2 :(得分:1)
您可能希望使用PHP's output_buffering configuration指令完全关闭该特定位置的输出缓冲。
Apache示例:
<Directory "/your/downloadable/files">
...
php_admin_value output_buffering "0"
...
</Directory>
“关闭”因为值似乎也有效,但它确实会引发错误。至少根据how other types are converted to booleans in PHP。 *耸肩 *
答案 3 :(得分:1)
过去想出这个想法(作为我的库的一部分)以避免高内存使用:
function suTunnelStream( $sUrl, $sMimeType, $sCharType = null )
{
$f = @fopen( $sUrl, 'rb' );
if( $f === false )
{ return false; }
$b = false;
$u = true;
while( $u !== false && !feof($f ))
{
$u = @fread( $f, 1024 );
if( $u !== false )
{
if( !$b )
{ $b = true;
suClearOutputBuffers();
suCachedHeader( 0, $sMimeType, $sCharType, null, !suIsValidString($sCharType)?('content-disposition: attachment; filename="'.suUniqueId($sUrl).'"'):null );
}
echo $u;
}
}
@fclose( $f );
return ( $b && $u !== false );
}
也许这会给你一些启发。
答案 4 :(得分:1)
如前所述:"Allowed memory .. exhausted" when using readfile,php文件顶部的以下代码块为我做了诀窍。
这将检查php输出缓冲是否处于活动状态。如果是这样,就把它关掉。
if (ob_get_level()) {
ob_end_clean();
}
答案 5 :(得分:0)
嗯,这是内存密集型功能。我会将用户管道到一个静态服务器,该服务器具有适当的规则集来控制下载而不是使用readfile()。
如果这不是一个选项,可以添加更多RAM来满足负载,或引入优雅控制服务器使用的排队系统。