Readfile从大文件中读取0个字节?

时间:2014-02-24 14:23:41

标签: php file-io zero abort

我正在尝试通过readfile()发送一个大文件。

但是,没有任何内容发送到浏览器,readfile()会返回0 false。)。

我正在尝试发送的文件是4GiB,大小可由PHP读取 我正在设置set_time_limit(0)以允许冗长的下载过程。

我尝试在4K块和fread()中使用echo的while循环装置发送文件,但是在下载500 - 2500 MiB之后随机中止(没有错误)并且从未设法完成下载。

以下测试代码

$f = '/var/www/tmp/largefile.dat';

var_dump(file_exists($f));
var_dump(is_readable($f));
var_dump(filesize($f));

$rf = readfile($f);
var_dump($rf);

产生以下输出:

  

bool(true)bool(true)int(4294967296)int(0)

使用以下命令创建测试文件:

dd if=/dev/zero of=largefile.dat bs=1M count=4096

我做错了什么以及如何解决?


编辑2014-07
升级到新的Apache2版本现在已经解决了这个问题。

1 个答案:

答案 0 :(得分:2)

PHP readfile在发送文件之前使用页面缓冲区来存储文件。如果由于内存不足而失败,则不会引发内存错误,只会失败。

确保执行下载的页面未使用页面缓冲区(http://us1.php.net/manual/es/ref.outcontrol.php

对于大文件来说,使用fopen fread读取文件并放入内容也是更好的选择。 还有fread,您可以制作一些代码以允许恢复下载。

这里有一个很好的例子:Resumable downloads when using PHP to send the file?


要禁用输出缓冲,您应该尝试使用ob_end_clean()在进程启动之前清理并结束缓冲区,或者使用ini_set('output_buffering',0)禁用输出缓冲;


在readfile文档中有一个关于如何将fread用于长文件而不是readfile的示例:

http://ca2.php.net/manual/en/function.readfile.php#48683

function readfile_chunked($filename,$retbytes=true) {
   $chunksize = 1*(1024*1024); // how many bytes per chunk
   $buffer = '';
   $cnt =0;
   $handle = fopen($filename, 'rb');
   if ($handle === false) {
       return false;
   }
   while (!feof($handle)) {
       $buffer = fread($handle, $chunksize);
       echo $buffer;
       ob_flush();
       flush();
       if ($retbytes) {
           $cnt += strlen($buffer);
       }
   }
       $status = fclose($handle);
   if ($retbytes && $status) {
       return $cnt; // return num. bytes delivered like readfile() does.
   }
   return $status;

} 

此外,还有一个在同一页面中提供部分下载支持的示例:

function smartReadFile($location, $filename, $mimeType='application/octet-stream') { 
  if(!file_exists($location)) { 
    header ("HTTP/1.0 404 Not Found");
    return;
  }

  $size=filesize($location);
  $time=date('r',filemtime($location));

  $fm=@fopen($location,'rb');
  if(!$fm) { 
    header ("HTTP/1.0 505 Internal server error");
    return;
  }

  $begin=0;
  $end=$size;

  if(isset($_SERVER['HTTP_RANGE'])) { 
     if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)){ 
      $begin=intval($matches[0]);
      if(!empty($matches[1])){ $end=intval($matches[1]); }
    }
  }

  if($begin>0||$end<$size){
    header('HTTP/1.0 206 Partial Content');
  }else{
    header('HTTP/1.0 200 OK'); 
  }

  header("Content-Type: $mimeType");
  header('Cache-Control: public, must-revalidate, max-age=0');
  header('Pragma: no-cache'); 
  header('Accept-Ranges: bytes');
  header('Content-Length:'.($end-$begin));
  header("Content-Range: bytes $begin-$end/$size");
  header("Content-Disposition: inline; filename=$filename");
  header("Content-Transfer-Encoding: binary\n");
  header("Last-Modified: $time");
  header('Connection: close'); 

  $cur=$begin;
  fseek($fm,$begin,0);

  while(!feof($fm)&&$cur<$end&&(connection_status()==0)) { 
    print fread($fm,min(1024*16,$end-$cur));
    $cur+=1024*16;
  }
}