Ограничение скорости скачивания файла в PHP

В предыдущей заметке я рассмотрел способы отдачи файла через скрипт PHP. Приведенная там функция file_download() позволяет отдать произвольный файл, имя которого может быть параметром скрипта или быть заданным константой. Скорость закачки этого файла будет определятся только возможностями сервера и канала связи между ним и клиентом.

В случае большого количества подключений к одному или нескольким файлам через скрипт, может наблюдаться перегрузка сервера. Один из методов борьбы с такой перегрузкой – это ограничение как скорости скачивания файла, так и числа подключений от одного клиента или IP-адреса. Поскольку файл у нас отдаётся клиенту программно, то и регулировать скорость отдачи файла и число подключений мы будем таким же образом.

Последняя реинкарнация функции file_download() принимает два параметра $filename – имя файла и $mimetype – его MIME-тип. Для введения ограничения по скорости закачки нам понадобится еще один параметр – скорость скачивания. Поскольку, этот параметр не относятся к самому файлу его резонно задавать для всего сайта, а не в параметрах, которые передаются функции. Сделаем это в её заголовке.

Ограничение скорости закачки

Модифицированный код будет выглядеть так:

function file_download($filename, $mimetype='application/octet-stream') {
// Задаем ограничение скорости закачки в байтах в секунду
// или ноль, если ограничений не требуется.
// Другим способом задания этого параметра может быть его определение
// через константу, посредством функции define(), в этом случае значение
// будет неизменным для любого запуска скрипта.
// Можно его значение задавать и снаружи функции исходя из каких-либо соображений,
// например, роли пользователя или загрузки сервера, и получать его
// посредством директивы global.
  $download_speed = 25000; // Около 25 килобайт в секунду.
// Задаём время дискретизации. С этой периодичностью клиенту будут отдаваться
// блоки данных считываемые из файла.
  $time_discret = 1;
  if (file_exists($filename)) {
    header($_SERVER["SERVER_PROTOCOL"] . ' 200 OK');
    header('Content-Type: ' . $mimetype);
    header('Last-Modified: ' . gmdate('r', filemtime($filename)));
    header('ETag: ' . sprintf('%x-%x-%x', fileinode($filename), filesize($filename), filemtime($filename)));
    header('Content-Length: ' . (filesize($filename)));
    header('Connection: close');
    header('Content-Disposition: attachment; filename="' . basename($filename) . '";');
    $f=fopen($filename, 'r');
// Проверяем задано ли ограничение скорости
    if((int) $download_speed > 0) {
      while(!feof($f)) {
// Включаем таймер
        $time_start = microtime(true);
// Читаем блок данных, которых мы должны отдать за время дискретизации
        echo fread($f, ceil($download_speed*$time_discret));
        flush();
// Находим время за которое наши данные отправлены
        $time_end = microtime(true);
        $time = $time_end - $time_start;
// Если время, оставшееся до конца времени дискретизации больше нуля,
// то приостанавливаем выполнение скрипта на величину этого времени в микросекундах.
        if($time_discret-$time > 0) usleep(($time_discret-$time)*1000000);
      }
    }
    else {
// Если у нас не задано ограничение скорости, то выполняем старый вариант кода
      while(!feof($f)) {
        echo fread($f, 1024);
        flush();
      }
    }
    fclose($f);
  } else {
    header($_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
    header('Status: 404 Not Found');
  }
  exit;
}

Как видно из кода, файл будет отдаваться клиенту дискретными порциями один раз в секунду. Можно уменьшить это значение поставив время дискретизации менее, чем 1 сек. Собственно, в общем случае так и нужно сделать. Хотя время ожидания клиентских программ посылки данных сервером обычно довольно велико, стоит учитывать и разные отклонения от этого правила. На мой взгляд время дискретизации в этом скрипте должно быть не более 0,1 секунды.

Интересно

Вот пытаюсь во всем этом разобраться, мозги уже пухнут... и как только люди это все придумывают =)

А так

Гуглят и компонуют чужой код.

Ну и что, зато очень полезно.

Ну и что, зато очень полезно. Щас буду разбираться с кодом мне как раз он нужен.

На неделе будет продолжение.

На неделе будет продолжение. Закачка с ограничением числа потоков.

появляется ошибка закачки

Спасибо большое разработчику, скрипт хороший.
Тестила в Денвере - работает на "ура!".
Однако перезалив скрипт на локальный сервер, где тоже стоит Apache+PHP+MySQL, скачка идет не до конца: где-то в середине пишет "Остановлено".
Скажите, с чем это может быть связано?

Таймаут времени выполнения скриптов. Это во-первых.

А во-вторых...

С ловлей ошибок при закачке замаешься. В начале вашего скрипта воткните error_reporting(E_ALL). Для тестирования не используйте IE - он удаляет недокачанные файлы, хотя их огрызки и можно найти в Temporary Internet Files. Используйте Opera или Firefox и откройте недокачаный файл в текстовом редакторе. Если закачка остановлена по ошибке, вы её увидите в начале или конце файла. Можно также использовать вывод ошибок в другое место. Загляните в error_log вашего сайта. В общем, ищите.

TimeOut

Происходит это через 60 секунд (по умолчанию), то есть timeout, можно в самом начале скрипта поставить функцию set_time_limit(0); (убирает лимит времени на выполнения скрипта вовсе), либо рассчитать точное время скачивания файла и прибавить 5 секунд на всякий случай.

Там как-то всё по другому.

Там как-то всё по другому. Т.е. имея таймаут в 60 сек скачивал этим скриптом по три минуты. Поэксперементируйте.

Спасибо большое за функцию

Спасибо большое за функцию

Ахренеть! Если сам сделал -

Ахренеть! Если сам сделал - молодец.

спасибо,помогло

спасибо,помогло

Помогите собрать скрипт!

Помогите! уже несколько дней не могу собрать этот скрипт до кучи. Не знаю какую функцию за какой писать. Дайте готовый скрипт (ограничение скорости и количества одновременных загрузок) в текстовом файле, пожалуйста, буду очень благодарен!

Вроде все тут есть

Вроде все тут есть http://shaman.asiadata.ru/node/314
Я прежде чем код публиковать проверяю его на тестовом серваке. Что именно у вас не работает и не собирается?

Я просто чайник в php

Я же не сказал, что скрипт не рабочий. Просто он разбит по частям, а я не знаю какой кусок за каким ставить. Вот и получаю постоянные ошибки на разных строчках. Ошибки синтаксические. Если бы готовый скрипт в текстовом файле... Чтобы залить на сеервер и пользоваться.

Если не чайник, то

1. Получение адреса файла в скрипт
2. Задать параметры скачивания.
3. Вызвать file_download() с нужными параметрами.

Если вы не специалист в PHP, то это сложно. Закажите готовый скрипт под ваши конкретные нужды.

Тут всё что мне нужно есть.

Тут все нужные мне функции есть, проблема у меня в том, что я не знаю как соединить одну функцию с другой: ставить одну за другой или одну в нутри другой. Если тестировали скрипт, значит он должен быть в готовом виде. Можно выложить куда нибудь?

Простой вариант

Простой вариант использования:
Ссылка на файл ./index.php?file=filename.ext
Файл index.php

if(isset($_GET['file'])) file_download($_GET['file']);

С этим ясно,нужно ограничение кол-ва скачек.

Как просто отдать браузеру файл - это ясно. Хочу полный скрипт с ограничением скорости и, главное, ограничения количества одновременных загрузок. По частям не могу собрать функции.

Ждите ответа... :)

Как видите такая статья у меня пока не написана, но планируется. Найду время - напишу.

Так есть же статья.

Вот здесь же статья: http://shaman.asiadata.ru/node/314 - я этот скрипт имел ввиду.

Вам же комбинация обоих

Вам же комбинация обоих способов нужна. Будет, но не сегодня.

Спасибо!

Спасибо! Буду очень ждать, т.к. мой vps задыхается уже.

спасибо, пригодилось

спасибо, пригодилось

Когда ставлю скорость

Когда ставлю скорость скачивания 100 кб/c, скорость потихоньку набирается.
Есть возможность сразу и начать со скоростью 100 кб/c, если да, то как такое реализовать?

Не забывайте про канал связи.

Не забывайте про канал связи. Инерция в наборе скорости связана именно с ним. А как этим управлять? Разве что отдавать сперва быстрее, потом устремлять скорость к нужной величине. Но вот определить функцию по времени для такого будет сложно. Да и меняться она будет от соединения к соединению.

Не забывайте про канал связи.

Не забывайте про канал связи. Инерция в наборе скорости связана именно с ним. А как этим управлять? Разве что отдавать сперва быстрее, потом устремлять скорость к нужной величине. Но вот определить функцию по времени для такого будет сложно. Да и меняться она будет от соединения к соединению.

direqtor, ну а почему-же на

direqtor, ну а почему-же на letitbit сразу качает с той скорости, которая и установлена?

Точная информация?

Точная информация?

На сервере ограничивайте ....

пример nginx'a
скармливайте nginx'у $path котрый регуляркой разбирайте
полностью получится что то такое

$ch_speed_limit = 1;//1 || 0 - ограничивать или нет.
//$data_dir_for_get - путь к файлу
// остальное для статистики...

$path = '/nginx/'.$ch_speed_limit.'/'.$s_id.'/'.$user_id.'/'.$fid.'/'.$data_dir_for_get;

header("Content-Type: ".GetCType($name));
header('Content-Transfer-Encoding: binary');
header("Content-Length: $size");
header("Ranges: {$_SERVER['HTTP_RANGE']}");
header("Content-Disposition: attachment; filename=".str_replace("+","_",urlencode($name)));
header("X-Accel-Redirect: $path");
exit();

Сейчас уже почти сплю, а вот

Сейчас уже почти сплю, а вот завтра погружусь в мануалы... :D

Это очень плохой способ. Для

Это очень плохой способ. Для каждого скачиваемого файла будет висеть процесс php в памяти + апач или другой серв. Так скорее сервер упрется в граничение по памяти чем в ограничение по скорости соединения. Для апача кстати есть отдельный модуль позволяющий регулировать скорость отдачи, называется mod_bandwidth.

Ну пока в одном месте все работает

Ну пока в одном месте все работает. Для того всё и писалось. А там посмотрим будем ли висеть при увеличении посещаемости. Тогда и до этого модуля доберусь.

Пытался я делать QoS+Shaper

Пытался я делать QoS+Shaper на php, все это херня товарищи, есть одна огромная проблема нерешаемая пока что в php, мы не можем определить, скачал ли юзвер ту порцию данных которую мы ему отдали и получается, что забивается буфер. Пример: поставили мы ограничение 10 мегабит, а у юзера скорость 56к модем, и вот в буфер все забилось, потом php прошел цикл слипа отдачи, и завершился успешно, а юзер нефига не докачал. Так еже еще одна проблема в том, что у многих хостеров стоит ограничение на время исполнения скрипта. Еще одна проблема это когда 20 человек качать будут апачик лопнит, ну памяти он уж очень много кушает.

Так что mod_bandwidth в руки или ngix

Да мне и так это понятно, что

Да мне и так это понятно, что для высоконагруженного проекта с тучей файла и посетителей способ не очень реальный. Но спасибо всем комментаторам - узнаю много нового, до чего сам бы долго добирался. :)

Чет я ничего не понял, на

Чет я ничего не понял, на локальном сервере работает, а на рабочем сайте не хотит(

Спасибо большое

Спасибо большое

ого, сколько комментариев.

ого, сколько комментариев. Похоже действительно большая проблема.