PHP

PHP 断点续传

Posted by eckid on September 27, 2016

PHP 断点续传

在开发中遇到一个问题,我需要提供下载的文件大小由十几MB变成了2.5G以上,原有的下载方式已经不能满足需求。

原因在于PHP在处理文件下载时,读取文件将会写入缓存,默认情况下php.ini中设置的缓存阈值memory-limit为128M。所以在较大文件进行直接读取下载时,会出现下载文件大小为0的情况。对于大文件,可以做断点续传的处理。

Range 与 Content-Range()

Range位于用户请求头中,指定第一个字节的位置和最后一个字节的位置,如(Range:200-300)

Content-Range用于响应头,表示整个资源中实体表示的字节范围。

请求下载整个文件:

GET  /test.rar  HTTP/1.1
Connection:  close
Host:  116.1.219.219
Range:  bytes=0-100

Range头域可以请求实体的一个或者多个子范围,Range的值为0表示第一个字节,也就是Range计算字节数是从0开始的

表示头500个字节:bytes=0-499

表示第二个500字节:bytes=500-999

表示最后500个字节:bytes=-500

表示500字节以后的范围:bytes=500-

第一个和最后一个字节:bytes=0-0,-1

同时指定几个范围:bytes=500-600,601-999

服务器将会返回HTTP/1.1 206 Partial Content,表示成功执行了范围(Range)请求。

PHP断点续传

function resumeBrokenDownloads($filePath) {
    set_time_limit(0);
    ini_set('memory_limit','1024M');

    if(!is_file($filePath)){
    	die("404 File not found!");
    }

    $filename = basename($filePath);
    header("Cache-Control: public");
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=".$filename);
    header("Content-Transfer-Encoding: binary");
    header("Accept-Ranges: bytes");
    $size = filesize($filePath);
    $range=0;

    if (isset($_SERVER['HTTP_RANGE'])){
        list($a,$range) = explode("=",$_SERVER['HTTP_RANGE']);
        $size2 = $size - 1;
        $new_length = $size2 - $range;
        header("HTTP/1.1 206 Partial Content");
        header("Content-Length: {$new_length}");
        header("Content-Range: bytes {$range}-{$size2}/{$size}");
    }else{
        $size2 = $size - 1;
        header("Content-Range: bytes 0-{$size2}/{$size}");
        header("Content-Length:".$size);
    }

    $fp = fopen("{$filePath}", "rb" );
    fseek($fp, $range);
        
    ob_clean();
    flush();
    while(!feof($fp)){
        print(fread($fp,1024*8));
        ob_flush();
        flush();
    }
    fclose($fp);
    exit();
}

flush()与 ob_flush()

php执行过程中,输出并非即刻执行,而是将echo或print的内容先写入buffer,当buffer满时,或者PHP执行完毕之后才会进行输出。php.ini的output_buffering参数用于配置buffer大小。

数据的流向为echo/pring -> php buffer -> tcp buffer -> browser。

位于buffer中的内容相当于处于一种等待输出状态,而flush()能将等待输出的内容输出到客户端。在开启了缓存的服务器环境中,脚本输出的内容进入了输出缓存中,尚未处于等待输出状态,此时用flush()将不会把这些内容发送到客户端,我么可以用ob_flush()将其设为等待输出状态,故在服务器中flush()需要和ob_flush()配套使用。

ob_flush() 把数据从PHP的缓冲中释放出来,即刷新PHP缓冲区

flush() 把不在缓冲中的或者说是被释放出来的数据发送到浏览器。

所以当缓冲存在的时候,我们必须ob_flush()flush()同时使用。正确使用的顺序是:先用ob_flush(),后用flush()

另外 ob_clean()用于清空先前缓冲区内容。