27

我在服务器上有一个 php 脚本来将文件发送给 recipents:他们得到一个唯一的链接,然后他们可以下载大文件。有时传输出现问题,文件已损坏或永远不会完成。我想知道是否有更好的方法来发送大文件

代码:

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

我见过诸如

http_send_file
http_send_data

但我不确定它们是否会起作用。

解决此问题的最佳方法是什么?

问候
欧翼

4

13 回答 13

15

分块文件是 PHP 中最快/最简单的方法,如果您不能或不想在Apache或一些专用脚本上使用cURL等更专业的东西。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移植。_ 在 200 MB 文件上进行了测试readfile(),即使最大允许内存限制设置为1G,该文件也会死掉,这是下载文件大小的五倍。

顺便说一句:我也在 files 上对此进行了测试>2GB,但 PHP 只能先写入2GB文件,然后断开连接。与文件相关的函数(fopen、fread、fseek)使用 INT,因此您最终会达到2GB. 在这种情况下,上述解决方案(即mod-xsendfile)似乎是唯一的选择。

编辑让自己100%将您的文件保存在utf-8. 如果您忽略它,下载的文件将被损坏。这是因为此解决方案用于print将文件块推送到浏览器。

于 2014-02-21T14:19:51.423 回答
12

如果您要发送真正的大文件并担心这会产生影响,您可以使用 x-sendfile 标头。

来自 SOQ using-xsendfile-with-apache-php,howto blog.adaniels.nl:how-i-php-x-sendfile/

于 2009-02-28T00:32:05.000 回答
7

最好的解决方案是依赖 lighty 或 apache,但如果在 PHP 中,我会使用PEAR 的 HTTP_Download(无需重新发明轮子等),具有一些不错的功能,例如:

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

请参阅介绍/使用文档

于 2009-03-06T04:49:07.470 回答
6

我们一直在几个项目中使用它,到目前为止它工作得很好:

/**
 * 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);
    }
}
于 2012-04-04T08:13:10.613 回答
4

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

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

于 2009-02-28T00:17:57.920 回答
3

创建指向实际文件的符号链接并将下载链接指向符号链接。然后,当用户单击 DL 链接时,他们将从真实文件中下载文件,但从符号链接中命名。创建符号链接需要几毫秒,这比尝试将文件复制到新名称并从那里下载要好。

例如:

<?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>

这就是我所做的,因为 Go Daddy 在 2 分 30 秒左右后终止了脚本的运行......这可以防止该问题并隐藏实际文件。

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

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

于 2010-11-08T18:40:26.803 回答
1

如果您使用 lighttpd 作为网络服务器,安全下载的替代方法是使用ModSecDownload。它需要服务器配置,但您将让网络服务器自己处理下载,而不是 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);
?>

当然,根据文件的大小,使用Unkwntechreadfile()提出的方法非常好。并且使用 garrow 提出的xsendfile是 Apache 也支持的另一个好主意。

于 2009-02-28T01:08:36.893 回答
1

当我过去这样做时,我使用过这个:

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.

这 3 行代码将完成下载的所有工作readfile()会将指定的整个文件流式传输到客户端,并确保设置无限时间限制,否则您可能会在文件完成流式传输之前用完时间。

于 2009-02-28T00:25:18.977 回答
1
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();
于 2012-08-16T07:57:05.487 回答
0

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

于 2010-01-22T21:39:44.147 回答
0

我使用了在 readfile 的 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;
}
于 2011-11-04T11:55:15.700 回答
0

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

于 2012-11-01T11:35:08.300 回答
0

这是在具有 256MB 内存限制的服务器上对大小为 200+ MB 的文件进行测试的。

header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=\"$file_name\"");
set_time_limit(0);
$file = @fopen($filePath, "rb");
while(!feof($file)) {
  print(@fread($file, 1024*8));
  ob_flush();
  flush();
}
于 2017-11-28T22:38:37.557 回答