Actualmente este singular problema puede ser resuelto por diversos módulos integrados en Apache (no de forma nativa pero si disponibles para su descarga), como MOD_LIMITIPCONN o MOD_BANDWIDTH. Sin embargo no en todos los servidores gratuitos o pagados está instalado.
Primero; ¿Por qué limitar las descargas?: Usualmente, uno acostumbra a bajar todos los links disponibles en una web y no uno por uno. Esto no afecta mayormente al cliente, pero sí al servidor, ya que consume el ancho de banda disponible. Y más aún al utilizar un gestor de descargas, que es capaz de conectarse a un mismo servidor N veces para bajar un mismo archivo, si dispones de 100 conexiones simultaneas disponibles, entonces el gestor facilmente podría utilizar esas 100 y dejar a todos los demás esperando.
Lo primero y único que necesitas: Apache (con mod_rewrite instalado) y mySQL (para almacenar las conexiones de cada IP).
El script consiste en, capturar todos los archivos terminados en “ZIP” o “RAR” o “ISO” (se pueden agregar todos los que quieras) y pasarlos por un archivo que va a decidir si acepta o no la descarga. No solo podemos limitar el número de descargas simultáneas, sino que también los KBS de velocidad. Lamentablemente, esta acción al aplicarse en archivos grandes, dejará al interprete de PHP ejecutandose durante todo el proceso, lo que consumirá recursos del procesador y RAM, por ende, NO USAR CON ARCHIVOS EXCESIVAMENTE GRANDES.
Se puede definir el máx. de descargas simultáneas y el tiempo de espera (en minutos) para volver a bajar archivos.
Primero
Deben crear una tabla en MySQL con el código:
CREATE TABLE `limitar`.`descargas` ( `id` INT NOT NULL AUTO_INCREMENT , `ip` VARCHAR( 15 ) NOT NULL , `archivo` VARCHAR( 255 ) NOT NULL , `tiempo_descarga` TIMESTAMP NOT NULL , PRIMARY KEY ( `id` ) ) ENGINE = MYISAM
RewriteEngine on RewriteRule ^(.*)\.(zip|rar|iso)$ limitar.php?archivo=$1.$2 [QSA]
y finalmente el código php necesario para esta labor (todas sus líneas comentadas):
Lamentablemente el parser de geshi, me transforma los simbolos < > en & lt; y & gt; respectivamente. Deben reemplazarlos para que funcione o bien descargar el rar.
Descargar el archivo limitar.php
<?php //------------------------------------- // evito el max execution time ini_set('max_execution_time', 0); // evito el timeout set_time_limit(0); // prevengo la detencion del script ignore_user_abort(true); // inicio el tiempo actual $tiempo_actual = time(); //------------------------------------- /* MIME TYPES */ // Necesarios para que iexplorer descargue el archivo // y no muestre simbolos raros (aunque si el mime no coincide, los mostrará igual) // Agregar todos los necesarios según el htaccess $mymes = array('rar' =>'application/x-rar-compressed', 'zip' => 'application/zip', 'iso' => 'application/octet-stream'); //------------------------------------- /* Estados */ // -1 = descarga fallida // 0 = descargando // 1 = descarga exitosa //------------------------------------- //------------------------------------- /* Parametros Generales */ // Descargas Máx. de forma simultanea $max_simultaneas = 2; // Tiempo de espera en segundos $tiempo_espera = 3600; //------------------------------------- /* Conexión con MySQL */ $recurso_sql = mysql_connect('localhost','root','') or die('MySQL no disponible'); if(!mysql_select_db('limitar',$recurso_sql)){ die('Base de datos seleccionada no disponible');} // Inicio en 0 bytes el maximo y el total descargado actualmente $actual = $maximo = 0; // prevengo ataques sobre la variable del archivo $extension = $_GET['extension']; $nombre_archivo = addslashes($_GET['archivo'].'.'.$extension); // completo el path hacia el archivo, incluyo la carpeta "files" $ruta_archivo = 'files/'.$nombre_archivo; // verifico que el archivo exista, y que tenga un nombre if (!file_exists($ruta_archivo) OR $nombre_archivo == '') { // Envio 404 archivo no existe header("HTTP/1.0 404 Not Found"); // termino el script die('Archivo no encontrado: <b>'.$nombre_archivo.'</b>'); } // Obtengo la ip del visitante // existen diversos metodos, y mucho mejores que el actual // sin embargo solo esta a metodo de ejemplo $ip = $_SERVER['REMOTE_ADDR']; // Busco si el archivo seleccionado se esta descargando desde la ip del cliente $sql = mysql_query('SELECT id FROM descargas WHERE ip="'.$ip.'" AND archivo="'.$nombre_archivo.'" AND estado=0 ', $recurso_sql); // obtengo las filas devueltas por el recurso $num = mysql_num_rows($sql); // se inicializa la variable de autorizacion como false $autorizado = false; // variable auxiliar, que permite saber si el tiempo de espera ha caducado para un registro en la bd $autorizado_update = false; // variable para saber si el usuario se encuentra bajando el archivo seleccionado $descargando = false; // si no existen registros (no se esta descargando el archivo actual en la ip actual) if($num == 0){ // se eliminan las descargas incompletas mysql_query('DELETE FROM descargas WHERE estado=-1 '); // Consulta que extrae de la base de datos todas las descargas por ip y ordenadas segun el tiempo_espera $sql = mysql_query('SELECT id,estado, tiempo_espera FROM descargas WHERE ip="'.$ip.'" AND estado !=-1 ORDER BY tiempo_espera ASC ', $recurso_sql); // se cuentan las coincidencias $num = mysql_num_rows($sql); // inicializo tiempo a esperar $tiempo_a_esperar = ($tiempo_actual+$tiempo_espera); // si no existen coincidencias o son inferiores al limite max de descargas simultaneas if($num == 0 OR $num < $max_simultaneas){ // se autoriza la descarga $autorizado = true; }else{ // se recogen los datos $datos = mysql_fetch_assoc($sql); // si la primera descarga esta con el tiempo de espera caducado y ya se descargo if($tiempo_actual > $datos['tiempo_espera'] AND $datos['estado'] == '1'){ // actualizo el tiempo de espera y se autoriza la descarga mysql_query('UPDATE FROM descargas SET tiempo_espera="'.$tiempo_a_esperar.'",archivo="'.$nombre_archivo.'",estado=0 WHERE id='.$datos['id'], $recurso_sql); // se pasa el identificador $identificador = $datos['id']; $autorizado_update = true; $autorizado = true; } } // de lo contrario, activo la variable que controla el estado del archivo }else{ $descargando = true; } // si no se encuentra autorizado, envio un error. if(!$autorizado){ header('HTTP/1.0 503 Service Unavailable'); // si se esta descargando arrojo un error if($descargando){ die('Ya te encuentras descargando el archivo seleccionado <b>'.$nombre_archivo.'</b>.'); }else{ die('No estas autorizado a descargar más de '.$max_simultaneas.' archivos de forma simultanea o a descargar esa cantidad sin esperar al menos '.$tiempo_espera.' minutos.'); } } // solo se inserta si el registro es nuevo if(!$autorizado_update){ // Se registra, automaticamente el estado es "descargando" mysql_query('INSERT INTO descargas (ip,archivo,tiempo_espera) VALUES ("'.$ip.'","'.$nombre_archivo.'","'.$tiempo_a_esperar.'") ', $recurso_sql); $identificador = mysql_insert_id($recurso_sql); } // calculo el peso de archivo $maximo = filesize($ruta_archivo); // cabeceras de control header('Content-type: '.$mymes[$extension]); header('Content-Disposition: attachment; filename="'.$nombre_archivo.'"'); header('Content-length: '.$maximo); header('Cache-control: private'); // abro el recurso de archivo en modo lectura $fd = fopen ($ruta_archivo, "r"); // mientras el recurso contenga bytes por leer while(!feof($fd)) { // compruebo si el usuario ha detenido la ejecucion del script if(connection_status()!= 0){ // envio funcion de salida comprobarDescarga(); // se detiene el script, el cliente se desconecto. die(); } // leo solo 10kb del archivo $buffer = fread($fd, 10240); // aumento la variable actual descargado $actual += $buffer; // muestro los 10kb que lei anteriormente echo $buffer; // Esta funcion me permite detener el while en microsegundos para limitar los kb de descarga usleep(300000); // En este caso, cada 1/3 de segundo se entregan 10kb, entonces el cliente tendrá disponible solo 30kb apróx. por segundo. } // cierro el recurso fclose ($fd); // envio la funcion de salida comprobarDescarga(); // termino el script die(); // Esta es la funcion de salida, que se ejecuta cuando el usuario cancela el script, o se realiza la descarga function comprobarDescarga(){ global $actual, $maximo, $recurso_sql, $tiempo_espera, $identificador; // renuevo el tiempo actual $tiempo_actual = time(); $tiempo_a_esperar = $tiempo_actual * $tiempo_espera; // si se descargo el archivo de forma correcta if($actual >= $maximo){ mysql_query('UPDATE descargas SET estado=1,tiempo_espera="'.$tiempo_a_esperar.'" WHERE id='.$identificador, $recurso_sql); // si se detuvo el script }else{ mysql_query('UPDATE descargas SET estado=-1 WHERE id='.$identificador, $recurso_sql); } // termino el script die(); } ?>
Descargar el archivo limitar.php
Son aproximadamente 170 líneas incluyendo los comentarios que son realmente necesarios para los novatos (aunque a veces, entorpecen la interpretación visual del script). Es el código completo probado y revisado por última vez el Viernes 16 de mayo del 2008. Realizarlo me tomó más de 2 horas, y 1 hora en realizar pruebas.
Existen varios ejemplos en la red, pero ninguno cubre temas como “detectar si el usuario se desconecto, si el archivo se bajo completamente, controlar un número de descargas simultáneas, etc…”
gracias!!!, estuve buscando un script similar y no habia encontrado nada de nada… me salvaste.
Es una pena, por que la base de datos superior no concuerda en diversos campos con el script