Ограничение числа подключений при скачивании файлов в PHP

Развивая тему закачки файлов через скрипты PHP с ограничением скорости рассмотрим ограничение количества скачиваемых файлов.

Для ограничения количества закачиваемых файлов, по какому-либо критерию, нам потребуется организовать обмен данными между двумя копиями одного скрипта, запущенными в пределах сеанса клиента. Это можно организовать через сохранение информации о запуске скрипта в базе данных или файле, установкой «куки» или использованием сессионных переменных. Правда, ограничения привязанные к сессии или cookie вряд ли могут надёжно помочь нам в этом случае. Клиенту достаточно очистить cookies или воспользоваться другим браузером или программой закачки. Единственный реальный на мой взгляд способ должен использовать ограничение количества закачиваемых файлов на основе IP-адреса клиента.

Продумаем функционал скрипта для описанной задачи. При запуске он должен выяснить IP-адрес клиента и получить число файлов уже скачиваемых с него. Если это число 0 или не превышает установленного ограничения, то скрипт должен увеличить его на единицу и начать отдачу файла, в противном случае отказать в доступе к файлу. По завершении работы скрипт должен уменьшить число скачиваемых файлов на единицу. Последнее условие очень важно, поскольку этот код должен выполняться даже при разрыве соединения самим клиентом, хотя в этом случае код может быть выполнен не полностью. Для того того чтобы обеспечить его выполнение придется использовать функцию register_shutdown_function(). Поскольку вопросы разных способов отдачи файла мы уже подробно рассмотрели ранее в готовых функциях, нас будет интересовать только их обвязка, ограничивающая число подключений с одного IP-адреса.

Итак, пусть наш скрипт закачки находится в файле file.php, а имя файла из того же каталога или из его подкаталога задается в запросе через параметр file. Код скрипта будет таким:

// Регистрируем функцию завершения скрипта. Функция должна увеличить на единицу значение    
// счетчика закачек для данного IP. Конкретная реализация этой функции может быть любой.  
// При обрыве соединения сервер сразу прекращает работу скрипта, поэтому увеличенное на    
// единицу значение счетчика закачек не будет уменьшено назад. Только через регистрацию    
// функции на завершение можно гарантировать ее вызов не только при нормальном завершении  
// работы, но и при обрыве соединения с клиентской или серверной стороны.                                
register_shutdown_function ("thread_stop");
// Стартуем данный поток. Эта функция должна увеличить на единицу значения счетчика числа  
// закачек с данного IP. Конкретная реализация этой функции может быть любой.
thread_start();
// Задаём количество потоков на одну сессию. Если  задано 0, то потоки не ограничиваются
$thread_limit = 3;
// Функция thread_number() возвращает нам номер текущего потока. Конкретная реализация этой  
// функции может быть любой.
if ($thread_limit > 0 && thread_number() > $thread_limit) {
// Если задано ограничение числа потоков и полученный номер текущего потока превышает лимит,
// то отдаём клиенту заголовок 503 (Сервис временно недоступен) примерное время ожидания    
// (240 секунд)и прерываем его выполнение. Можно вместо этого при помощи функции header()    
// перенаправить его на специальную страницу, где будет описана причина ограничений:        
// превышение числа одновременно закачиваемых файлов с одного IP-адреса.                                                                      
  header($_SERVER["SERVER_PROTOCOL"] . " 503 Service Temporarily Unavailable");
  header("Status: 503 Service Temporarily Unavailable");
  header("Retry-After: 240");
  header("Connection: Close");
  exit();
}
else {
// Отдаем клиенту файл при помощи любой из функций, описанных в предыдущих заметках. Имя    
// файла для закачки передается через переменную file.
  file_download($_GET['file']);
}

Функция file_download() может применяться любая из моих предыдущих заметок. Либо просто передающая файл на закачку напрямую или через скрипт, либо ограничивающая скорость закачки.

Ну а реализация функций thread_start(), thread_stop() и thread_number() возможна посредство сохранения данных как в БД, так и в файловой системе сайта. Хотя стоит отметить, что в PHP 5 имеются и другие способы обмена данными между скриптами.

Сохранение данных в файловой системе

Одним из способов организовать возможность сохранения данных для нескольких запускаемых копий одного PHP-скрипта, являются файлы, уникальные для каждого IP-адреса и каждого потока закачки с него. Разместим в папке нашего скрипта подпапку с названием lock и при подключении каждого IP-адреса будем создавать в ней папки с именем соответствующим этому IP. Для каждого потока закачки файлов мы будем создавать в этих папках файлы нулевого размера. Т.е. если с адреса 192.168.0.1 у нас качается два файла, то в соответствующей папке (lock/192.168.0.1) у нас будут находится два файла 1 и 2.
Код требуемых нам функций thread_number(), thread_start() и thread_stop() будет следующим.

Функция thread_number()

// Эта функция возвращает номер текущего потока для данного IP-адреса
function thread_number(){
// Задаем имя папки для размещения файлов отмечающих каждый поток
// В папке lock каждому IP-адресу будет соответствовать папка с именем равным IP
  $lock_dir =  './lock/' . $_SERVER['REMOTE_ADDR'];
// Очищаем кэш файловых операций
  clearstatcache();
// Если папка не существует (закачка с данного IP еще не производилась), то создаем ее
// с правами на полный доступ
  if(!is_dir($lock_dir)) {
    mkdir($lock_dir);
        chmod($lock_dir, 0777);
  }
// Получаем список файлов в папке
  $files = scandir($lock_dir);
// Поскольку даже в пустой папке присутствуют два системных имени:
//  "." - текущая папка и ".." - родительская папка, то удаляем их из списка
  array_shift($files);
  array_shift($files);
// Возвращаем количество файлов. Это и будет номер текущего потока запущенного с IP-адреса
  return count($files);
}

Функция thread_start()

// Эта функция, запускаемая при начале скрипта, будет отмечать каждый поток, для каждого IP-адреса
function thread_start(){
// Задаём имя файла для отметки текущего потока.
// Он будет на единицу больше числа возвращаемого функцией thread_number()
  $lock_file =  './lock/' . $_SERVER['REMOTE_ADDR'] .'/'. (thread_number()+1);
// Эти две функции создадут нам файл нулевого размера.
  $f = fopen($lock_file, 'w');
  fclose($f);
// Выставляем права на файл для его беспрепятственного удаления в конце работы скрипта
  chmod($lock_file, 0777);
}

Функция thread_stop()

// Эта функция удаляет файл отмечающий данный поток для данного IP-адреса или всю папку
function thread_stop(){
// Задаем имя папк, где размещены файлы отмечающих каждый поток для текущего IP.
  $lock_dir =  './lock/' . $_SERVER['REMOTE_ADDR'];
// Получаем номер текущего скрипта. В силу того, что для любого его запуска создается свой файл
// эта величина всегда будет больше единицы
  $number = thread_number();
  if($number==1) {
// При завершении единственного скрипта, запущенного с данного IP будут удалены и папка и файл
    unlink($lock_dir .'/'. $number);
    rmdir($lock_dir);
  }
  elseif($number>1) {
// Если кроме нашего скрипты запущены другие его копии с этого же IP, удаляем только файл
    unlink($lock_dir .'/'. $number);
  }
  else{
// Данная ситуация невозможна при корректной работе скрипта, но все равно отследим ее.
// Если в папке нет ни одного файла, просто удаляем папку.
    rmdir($lock_dir);
  }
}

Сохранение данных в MySQL

Второй доступный способ сохранения данных о количестве закачек – это сохранение информации в базе данных. Для каждого IP-адреса мыбудем создавать отдельную запись со счетчиком потоков. Код при этом оказывается проще, чем в предыдущем случае – мы всего лишь выполняем простые SQL-запросы.

Создадим требуемую таблицу:

CREATE TABLE threads (
  `ip` int(10) unsigned NOT NULL,
  `count` smallint(5) unsigned NOT NULL,
  PRIMARY KEY  (`ip`)
)

Код требуемых нам функций thread_number(), thread_start() и thread_stop() будет следующим.

Функция thread_number()

// Эта функция возвращает номер текущего потока для данного IP-адреса
function thread_number(){
// Выполняем запрос для записи с ключом
// соответствующим числовому значению IP-адреса
  $res = mysql_query("SELECT `count` FROM `threads` WHERE `ip` = ". ip2long($_SERVER['REMOTE_ADDR']));
// Если запись присутствует,
// то возвращаем указанное в ней число потоков
  if ($line = mysql_fetch_array($res, MYSQL_ASSOC)) return (int) $line['count'];
// Если записи нет возвращаем ноль
  return 0;
}

Функция thread_start()

// Эта функция, запускаемая при начале скрипта, будет отмечать каждый поток, для каждого IP-адреса
function thread_start(){
//  Получаем номер очередного потока
  if(thread_number() == 0) {
// Если потоков с этого IP не зарегистрировано,
// то создаём для него запись
    mysql_query("INSERT INTO `threads` (`ip`, `count`) VALUES (
"
. ip2long($_SERVER['REMOTE_ADDR']) .", 1)");
  } else {
// Если с данного IP уже скачиваются потоки,
// увеличиваем счётчик на единицу
    mysql_query("UPDATE `threads` SET `count` = `count` + 1 WHERE `ip` = ". ip2long($_SERVER['REMOTE_ADDR']);
  }
}

Функция thread_stop()

// Эта функция отмечает окончание данного потока для данного IP-адреса
function thread_stop(){
  $number = thread_number();
  if($number == 1) {
// Если остался один поток,
// то запись из таблицы удаляется
    mysql_query("DELETE FROM `threads` WHERE `ip` = ". ip2long($_SERVER['REMOTE_ADDR']));
  }
  else {
// Если все еще есть активные потоки,
// то счетчик уменьшается на единицу
    mysql_query("UPDATE `threads` SET `count` = `count` - 1 WHERE `ip` = ". ip2long($_SERVER['REMOTE_ADDR']));
  }
}

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

Ограниченный 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
А не робот ли вы случайно?
1 + 1 =
Решите эту простую математическую задачу и введите результат. Например, для 1+3, введите 4.