fread a lot slower for downloads than readfile

2020-04-07 18:51发布

I'm serving downloads from a URL to my users through a PHP script. When using readfile() I get the maximum download speed my connection can support (about 2.5MB/s) however when I use the fopen, fread, fclose route the download speed is very, very slow (about 1-2KB/s).

Here's my code:

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $filename);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . $filesize);
ob_clean();
flush();

$file = fopen($url, 'rb');

while(!feof($file)) {
    echo fread($file, 2014);
}

and the readfile code is simply readfile($link);.

I can't just use the readfile() function because of two reasons, one is I want to restrict the users download speed (which I can do with fread by only reading so much data) and I also want to track how much a user is downloading (I can do this with readfile() but it doesn't count partial downloads).

Does anyone know why this might be happening or how I can fix it? As far as I know readfile() is just a wrapper for fopen, fread and fclose so I don't get what's going wrong.

Edit: Ended up going with cURL for this.

$curl = curl_init();
$options = array(
    CURLOPT_URL => $rdLink,
    CURLOPT_FAILONERROR => true,
    CURLOPT_BINARYTRANSFER => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_WRITEFUNCTION => 'readResponse'
);
curl_setopt_array($curl, $options);

if(!curl_exec($curl)) {
    header('Location: http://whatever.com');
    exit;
}
curl_close($curl);

function readResponse($ch, $data) {
    $length = mb_strlen($data, '8bit');

    echo $data;

    return $length;
}

3条回答
迷人小祖宗
2楼-- · 2020-04-07 19:29

It might be buffering (or perhaps even rate-limiting) somewhere in PHP or Apache. Try changing:

while(!feof($file)) {
   echo fread($file, 2014);
}

To:

while(!feof($file)) {
   $s=fread($file, 2014);
   if($s===false)break;  //Crude
   echo $s;
   @ob_flush();@flush();
}

(The @ prefix is because they might complain about empty buffers.)

As mr.freshwater said, you ought to have error-checking on your fread call, so I've added something basic above.

查看更多
再贱就再见
3楼-- · 2020-04-07 19:34

Use stream_context_create() and stream_get_contents()

$context = stream_context_create();
$file = fopen($url, 'rb', FALSE, $context);
while(!feof($file))
{
    //usleep(1000000);
    echo stream_get_contents($file, 2014);
}

You could also try making the file read length larger and putting in a usleep() to slow execution. The stream functions seem to be recommended over fread for the latest version of PHP anyway. You might want to also prepend an @ in front of the fread() or stream_get_contents() to suppress any errors, at least in production. Without it, and a little mishap, and you have a corrupted file.

查看更多
再贱就再见
4楼-- · 2020-04-07 19:37

The reason is 2014. OS gets 4096 byte data portion very fast then others. But if you write 2014, then OS tries to read data from file by one byte to calculate it. That's why it takes so long time. Change 2014 to 4096

查看更多
登录 后发表回答