使用 PHP 流式传输大文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6914912/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Streaming a large file using PHP
提问by Filo Stacks
I have a 200MB file that I want to give to a user via download. However, since we want the user to only download this file once, we are doing this:
我有一个 200MB 的文件,我想通过下载提供给用户。但是,由于我们希望用户只下载此文件一次,因此我们这样做:
echo file_get_contents('http://some.secret.location.com/secretfolder/the_file.tar.gz');
to force a download. However, this means that the whole file has to be loaded in memory, which usually doesn't work. How can we stream this file to them, at some kb per chunk?
强制下载。但是,这意味着必须将整个文件加载到内存中,这通常是行不通的。我们如何以每块一些 kb 的速度将这个文件流式传输给他们?
回答by diagonalbatman
Try something like this (source http://teddy.fr/2007/11/28/how-serve-big-files-through-php/):
尝试这样的事情(来源http://teddy.fr/2007/11/28/how-serve-big-files-through-php/):
<?php
define('CHUNK_SIZE', 1024*1024); // Size (in bytes) of tiles chunk
// Read a file and display its content chunk by chunk
function readfile_chunked($filename, $retbytes = TRUE) {
$buffer = '';
$cnt = 0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, CHUNK_SIZE);
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;
}
// Here goes your code for checking that the user is logged in
// ...
// ...
if ($logged_in) {
$filename = 'path/to/your/file';
$mimetype = 'mime/type';
header('Content-Type: '.$mimetype );
readfile_chunked($filename);
} else {
echo 'Tabatha says you haven\'t paid.';
}
?>
回答by Darren Gordon
Use fpassthru()
. As the name suggests, it doesn't read the entire file into memory prior to sending it, rather it outputs it straight to the client.
使用fpassthru()
. 顾名思义,它不会在发送之前将整个文件读入内存,而是直接将其输出到客户端。
Modified from the example in the manual:
从手册中的示例修改:
<?php
// the file you want to send
$path = "path/to/file";
// the file name of the download, change this if needed
$public_name = basename($path);
// get the file's mime type to send the correct content type header
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $path);
// send the headers
header("Content-Disposition: attachment; filename=$public_name;");
header("Content-Type: $mime_type");
header('Content-Length: ' . filesize($path));
// stream the file
$fp = fopen($path, 'rb');
fpassthru($fp);
exit;
If you would rather stream the content directly to the browser rather than a download (and if the content type is supported by the browser, such as video, audio, pdf etc) then remove the Content-Disposition header.
如果您更愿意将内容直接流式传输到浏览器而不是下载(并且如果浏览器支持内容类型,例如视频、音频、pdf 等),则删除 Content-Disposition 标头。
回答by rid
Take a look at the example from the manual page of fsockopen()
:
查看以下手册页中的示例fsockopen()
:
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
This will connect to www.example.com
, send a request then get and echo the response in 128 byte chunks. You may want to make it more than 128 bytes.
这将连接到www.example.com
,发送请求,然后以 128 字节块的形式获取并回显响应。您可能希望使其超过 128 个字节。
回答by BIDS Salvaterra
I found this method in http://codesamplez.com/programming/php-html5-video-streaming-tutorial
我在http://codesamplez.com/programming/php-html5-video-streaming-tutorial 中找到了这个方法
And it works very well for me
它对我很有效
<?php
class VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath)
{
$this->path = $filePath;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: video/mp4");
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
To use this class, you will have to write simple code like as below:
要使用这个类,你必须编写如下简单的代码:
$stream = new VideoStream($filePath);
$stream->start();
回答by Gilly
I ran into this problem as well using readfile() to force a download. The memory problem lies not within readfile, rather with ouput buffering.
我也遇到了这个问题,使用 readfile() 强制下载。内存问题不在于读取文件,而在于输出缓冲。
Just make sure you switch off output buffering before readfile, and the problem should be fixed.
只要确保在读取文件之前关闭输出缓冲,问题就应该得到解决。
if (ob_get_level()) ob_end_clean();
readfile($yourfile);
Works for files with a size much larger than the allocated memory limit.
适用于大小远大于分配的内存限制的文件。