докачка и производительность - помогите разобраться

glam

Новичок
докачка и производительность - помогите разобраться

Всем привет!
В свое время слямзил в интернете код, который отдает файлы с поддержкой докачки.
Отмечу, что насколько я понял, качаются файлы нормально, в несколько потоков и докачка поддерживается - проверял, скачивая программой Free Download Manager.
В последнее время с ростом количества посетителей беспокоит производительность - сайт стал заметно тормозить.
Решил внести "тюнинг" в код и добавить паузу, чтобы понизить скорость скачки. И код привел меня в некоторое недоумение.
Сейчас пытаюсь разобраться в коде и столкнулся с некоторыми проблемами. Сначала приведу код, который я использую и дополню его своими комментариями с вопросами:
PHP:
    // здесь все понятно, проверяем, существует ли файл, если нет - то посылаем в браузер ошибку
    // что файл не найден
    if (!file_exists($filename)) 
    { 
      header ("HTTP/1.0 404 Not Found"); 
      exit; 
    }
    // здесь понятно, определяем размер, дату файла и получаем указатель, если не получилось
    // получить указатель - посылаем в браузер ошибку, что запрещен доступ к файлу
    $fsize = filesize($filename); 
    $ftime = date("D, d M Y H:i:s T", filemtime($filename)); 
    $fd = @fopen($filename, "rb"); 
    if (!$fd)
    { 
      header ("HTTP/1.0 403 Forbidden"); 
      exit; 
    }
    // здесь становится интересно, насколько я понял если браузер поддерживает докачку,
    // то получаем диапазон и смещаем указатель файла на этот диапазон
    if ($HTTP_SERVER_VARS["HTTP_RANGE"])
   { 
      $range = $HTTP_SERVER_VARS["HTTP_RANGE"]; 
      $range = str_replace("bytes=", "", $range); 
      $range = str_replace("-", "", $range); 
      if ($range)
      {
        fseek($fd, $range);
      } 
    }
    // а вот тут нипанятнаааа:
    // 1) почему читаем все сразу? Халатность в программировании? Или я что-то не понимаю?
    // 2) Почему не уменьшили длину чтения на значение смещения? Ошибка в программировании?
    // Или я что-то не понимаю?
    $content = fread($fd, filesize($filename)); 
    fclose($fd);
    // здесь понятно, сообщаем браузеру. отдаем ли кусочками или нет
    if ($range) { 
      header("HTTP/1.1 206 Partial Content"); 
    } 
    else { 
      header("HTTP/1.1 200 OK"); 
    }
    // здесь понятно, посылаем информацию о файле
    header("Content-Disposition: attachment; filename=$fn"); 
    header("Last-Modified: $ftime"); 
    header("Accept-Ranges: bytes"); 
    header("Content-Length: ".($fsize-$range)); 
    // эта строчка непонятна, почему говорим браузеру, 
    // что диапазон отдачи - ($fsize -1)."/".$fsize)
    // я вообще не понимаю, что такое минус один и что за число мы получаем
    header("Content-Range: bytes $range-".($fsize -1)."/".$fsize); 
    header("Content-type: application/octet-stream");
    // понятно, отдаем файл (или часть)
    print $content;
-~{}~ 18.10.07 14:29:

И еще вопрос.
Насколько я понимаю, в коде нет цикла частичной отдачи файла и цикл есть коде самого браузера, браузер циклично обращается к скрипту, а мой код итерационно получает запрос с определенным смещением и тдает запрашиваемые куски. Это моя догадка (домысел) - я правильно понимаю?

-~{}~ 18.10.07 14:45:

Насколько я понимаю, нужно убрать строчку
PHP:
  $content = fread($fd, filesize($filename));
И добавить после посылки всех заголовков, вместо строки
PHP:
print $content;
строчки:
PHP:
while(!eof($fd))
{
  $content = fread($fd, 1024);
  print $content;
  // чтобы получить скорость 50 килобайт в секунду - 
  // прочитаем один килобайт и заснем на 1/50 секунды
  sleep(1/50); 
}
Ничего не поломается с докачкой?
Я честно говоря не понимаю, как работают ranges в указанном коде. Ведь приведенный код получает команду ОТКУДА читать, но не получает команду - СКОЛЬКО читать. И если браузер запускает несколько потоков скачки, то как приведенный код отдаст в потоке браузера столько сколько нужно? Ведь код читает все до конца файла в любом потоке? И вообще говоря получается, что получаются зоны дублирования? Или браузер сам принудительно обрывает поток и обращение к скрипту, когда получил сколько нужно в текущем потоке?
 

SelenIT

IT-лунатик :)
glam, как видно из кода, это именно докачка (напр., после обрыва соединения), а не закачка частями в несколько потоков, как делают FlashGet и т.п.. Код, однозначно, неоптимальный, рациональнее было бы использовать fpassthru.

"Content-Range: bytes $range-".($fsize -1)."/".$fsize - так по rfc2616: "байты с такого-то по такой-то (нумерация с нуля) из столька-то". В данном случае - все оставшиеся (с места обрыва прошлой попытки). Для отдачи частями нужен более сложный разбор HTTP_RANGE.

А sleep(1/50) работать не будет - аргументом должно быть целое. И вообще заставлять скрипт спать при высокой нагрузке - имхо, плохая идея, ведь получится, что "сонные" скрипты пачками подолгу висят в памяти вместо того, чтоб быстро отработать и освободить ее. Лучше оптимизировать расход памяти (заменой fread+print на fpassthru) и т.п.
 

glam

Новичок
SelenIT, теперь понятно с рэйнджами. Логически на докачку похоже. Но каким-то чудесным образом моя качалка успешно открывает несколько потоков и даже отображает графики открытых потоков.
С Content-Range тоже понял.
С функцией sleep тоже понятно - использовать, например, так: usleep(1000/50);
Заставить спать скрипты плохо - согласен. Такой метод не покатит.
Пролшу пояснить фразу:
Лучше оптимизировать расход памяти (заменой fread+print на fpassthru) и т.п. .
Прочел доку по fpassthru по ссылке php.net,
fpassthru -- Выводит все оставшиеся данные из файлового указателя
Не понимаю, в чем отличия от fread и какие преимущества дает. Если не сложно, приведите пример правильного использования. Я пока начинаю курить учебник.
 
Сверху