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); // Читаем блок данных, которых мы должны отдать за время дискретизации echofread($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)){ echofread($f,1024); flush(); } } fclose($f); }else{ header($_SERVER["SERVER_PROTOCOL"].' 404 Not Found'); header('Status: 404 Not Found'); } exit; }
Как видно из кода, файл будет отдаваться клиенту дискретными порциями один раз в секунду. Можно уменьшить это значение поставив время дискретизации менее, чем 1 сек. Собственно, в общем случае так и нужно сделать. Хотя время ожидания клиентских программ посылки данных сервером обычно довольно велико, стоит учитывать и разные отклонения от этого правила. На мой взгляд время дискретизации в этом скрипте должно быть не более 0,1 секунды.