在PHP中可靠地下载大文件

我有一个服务器上的PHP脚本发送文件到配方:他们得到一个独特的链接,然后他们可以下载大文件。 有时传输出现问题,文件损坏或永不结束。 我想知道是否有更好的方式发送大文件

码:

$f = fopen(DOWNLOAD_DIR.$database[$_REQUEST['fid']]['filePath'], 'r'); while(!feof($f)){ print fgets($f, 1024); } fclose($f); 

我见过的function如

 http_send_file http_send_data 

但我不确定他们是否会工作。

解决这个问题的最好方法是什么?

问候
erwing

如果你正在发送真正的大文件,并担心这会有影响,你可以使用x-sendfile头。

从SOQ 使用-xsendfile-with-apache-php ,howto blog.adaniels.nl:how-i-php-x-sendfile /

最好的解决scheme是依赖lighty或apache,但是如果在PHP中,我会使用PEAR的HTTP_Download (不需要重新发明轮子等),有一些很好的function,比如:

  • 基本的限制机制
  • 范围(部分下载和恢复)

请参阅介绍/使用文档 。

分块文件是PHP中最快速/最简单的方法,如果你不能或不想使用一些更专业的东西,比如cURL , Apache上的mod-xsendfile或者一些专门的脚本 。

 $filename = $filePath.$filename; $chunksize = 5 * (1024 * 1024); //5 MB (= 5 242 880 bytes) per one chunk of file. if(file_exists($filename)) { set_time_limit(300); $size = intval(sprintf("%u", filesize($filename))); header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); header('Content-Length: '.$size); header('Content-Disposition: attachment;filename="'.basename($filename).'"'); if($size > $chunksize) { $handle = fopen($filename, 'rb'); while (!feof($handle)) { print(@fread($handle, $chunksize)); ob_flush(); flush(); } fclose($handle); } else readfile($path); exit; } else echo 'File "'.$filename.'" does not exist!'; 

从richnetapps.com / NeedBee移植。 testing了200 MB的文件,其中readfile()死了,即使最大允许的内存限制设置为1G ,这是下载的文件大小的五倍。

顺便说一句:我也testing了这个文件>2GB ,但PHP只能设法写入第一个2GB的文件,然后打破了连接。 与文件相关的函数(fopen,fread,fseek)使用INT,所以你最终达到了2GB的限制。 上面提到的解决scheme(即mod-xsendfile )似乎是这种情况下唯一的select。

编辑让你自己100% ,你的文件保存在utf-8 如果你省略了,下载的文件将被损坏。 这是因为这个解决scheme使用print将一个文件的块推送到浏览器。

创build一个到实际文件的符号链接,并在符号链接上下载链接点。 然后,当用户点击DL链接时,他们将从实际文件中获得文件下载,但是从符号链接命名。 创build符号链接需要几毫秒的时间,并且比试图将文件复制到新名称并从那里下载要好。

例如:

 <?php // validation code here $realFile = "Hidden_Zip_File.zip"; $id = "UserID1234"; if ($_COOKIE['authvalid'] == "true") { $newFile = sprintf("myzipfile_%s.zip", $id); //creates: myzipfile_UserID1234.zip system(sprintf('ln -s %s %s', $realFile, $newFile), $retval); if ($retval != 0) { die("Error getting download file."); } $dlLink = "/downloads/hiddenfiles/".$newFile; } // rest of code ?> <a href="<?php echo $dlLink; ?>Download File</a> 

这是我做的,因为去爸爸杀死脚本运行2分30秒左右….这可以防止这个问题,并隐藏实际的文件。

然后,您可以设置一个CRON作业来定期删除符号链接….

这整个过程然后将文件发送到浏览器,不pipe它运行多长时间,因为它不是一个脚本。

我们已经在几个项目中使用了这个function,到目前为止效果还不错:

 /** * Copy a file's content to php://output. * * @param string $filename * @return void */ protected function _output($filename) { $filesize = filesize($filename); $chunksize = 4096; if($filesize > $chunksize) { $srcStream = fopen($filename, 'rb'); $dstStream = fopen('php://output', 'wb'); $offset = 0; while(!feof($srcStream)) { $offset += stream_copy_to_stream($srcStream, $dstStream, $chunksize, $offset); } fclose($dstStream); fclose($srcStream); } else { // stream_copy_to_stream behaves() strange when filesize > chunksize. // Seems to never hit the EOF. // On the other handside file_get_contents() is not scalable. // Therefore we only use file_get_contents() on small files. echo file_get_contents($filename); } } 

对于下载文件,我能想到的最简单的方法是将文件放在临时位置,给他们一个唯一的URL,他们可以通过常规的HTTP下载。

作为生成这些链接的一部分,您还可以删除超过X小时的文件。

当我在过去做过这些事情时,我已经使用了这个:

 set_time_limit(0); //Set the execution time to infinite. header('Content-Type: application/exe'); //This was for a LARGE exe (680MB) so the content type was application/exe readfile($fileName); //readfile will stream the file. 

这三行代码将完成下载的所有工作。readfile()会将指定给客户端的整个文件进行stream式处理,并确保设置无限的时间限制,否则在文件完成stream式处理之前可能会耗尽时间。

如果您使用lighttpd作为networking服务器,则安全下载的替代方法是使用ModSecDownload 。 它需要服务器configuration,但你会让web服务器处理下载本身,而不是PHP脚本。

生成下载URL将如下所示(从文档中获取),并且当然只能为授权用户生成:

 <?php $secret = "verysecret"; $uri_prefix = "/dl/"; # filename # please note file name starts with "/" $f = "/secret-file.txt"; # current timestamp $t = time(); $t_hex = sprintf("%08x", $t); $m = md5($secret.$f.$t_hex); # generate link printf('<a href="%s%s/%s%s">%s</a>', $uri_prefix, $m, $t_hex, $f, $f); ?> 

当然,根据文件大小的不同,使用Unkwntech提出的readfile()也是非常优秀的。 而使用garrow提出的xsendfile是Apache也支持的另一个好主意。

 header("Content-length:".filesize($filename)); header('Content-Type: application/zip'); // ZIP file header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="downloadpackage.zip"'); header('Content-Transfer-Encoding: binary'); ob_end_clean(); readfile($filename); exit(); 

我不确定这是一个大文件的好主意。 如果下载脚本的线程一直运行,直到用户完成下载,并且您正在运行诸如Apache之类的东西,则只有50个或更多的并发下载可能会导致服务器崩溃,因为Apache并不是devise用来运行大量的长时间运行线程在同一时间。 当然,我可能是错的,如果apache线程以某种方式终止,下载进程中的下载位于缓冲区。

我已经在php手册条目的注释中使用了下面的代码片段来读取文件:

 function _readfileChunked($filename, $retbytes=true) { $chunksize = 1*(1024*1024); // how many bytes per chunk $buffer = ''; $cnt =0; // $handle = fopen($filename, 'rb'); $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; } 

我有同样的问题,我的问题通过在开始会话之前添加此问题解决session_cache_limiter('none');