Ограничение скорости скачивания файла в 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 секунды.

Добавить комментарий

Ограниченный HTML

  • Допустимые HTML-теги: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
CAPTCHA
А не робот ли вы случайно?
10 + 0 =
Решите эту простую математическую задачу и введите результат. Например, для 1+3, введите 4.