¿Cómo forzar la descarga de archivos grandes sin utilizar demasiada memoria?

Estoy intentando servir grandes archivos zip a los usuarios. Cuando hay 2 conexiones concurrentes, el servidor se queda sin memoria (RAM). Aumenté la cantidad de memoria de 300MB a 4GB (Dreamhost VPS) y luego funcionó bien.

Necesito permitir mucho más de 2 conexiones simultáneas. Los 4GB reales permitirían algo así como 20 conexiones simultáneas (muy mal).

Bueno, el código actual que estoy usando necesita el doble de memoria que el tamaño real del archivo. Eso es muy malo. Quiero algo como “transmitir” el archivo al usuario. Por lo tanto, no asignaría más que el fragmento que se está sirviendo a los usuarios.

El siguiente código es el que estoy usando en CodeIgniter (framework PHP):

ini_set('memory_limit', '300M'); // it was the maximum amount of memory from my server set_time_limit(0); // to avoid the connection being terminated by the server when serving bad connection downloads force_download("download.zip", file_get_contents("../downloads/big_file_80M.zip"));exit; 

La función force_download es la siguiente (función CodeIgniter default helper):

 function force_download($filename = '', $data = '') { if ($filename == '' OR $data == '') { return FALSE; } // Try to determine if the filename includes a file extension. // We need it in order to set the MIME type if (FALSE === strpos($filename, '.')) { return FALSE; } // Grab the file extension $x = explode('.', $filename); $extension = end($x); // Load the mime types @include(APPPATH.'config/mimes'.EXT); // Set a default mime if we can't find it if ( ! isset($mimes[$extension])) { $mime = 'application/octet-stream'; } else { $mime = (is_array($mimes[$extension])) ? $mimes[$extension][0] : $mimes[$extension]; } // Generate the server headers if (strpos($_SERVER['HTTP_USER_AGENT'], "MSIE") !== FALSE) { header('Content-Type: "'.$mime.'"'); header('Content-Disposition: attachment; filename="'.$filename.'"'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header("Content-Transfer-Encoding: binary"); header('Pragma: public'); header("Content-Length: ".strlen($data)); } else { header('Content-Type: "'.$mime.'"'); header('Content-Disposition: attachment; filename="'.$filename.'"'); header("Content-Transfer-Encoding: binary"); header('Expires: 0'); header('Pragma: no-cache'); header("Content-Length: ".strlen($data)); } exit($data); } 

Intenté algunos códigos basados ​​en fragmentos que encontré en Google, pero el archivo siempre se entregó dañado. Probablemente debido al mal código.

¿Alguien podría ayudarme?

Hay algunas ideas en este hilo . No sé si el método readfile () ahorrará memoria, pero parece prometedor.

¿Estás enviando los contenidos ($ data) de este archivo a través de PHP?

Si es así, cada proceso de Apache que maneje esto terminará creciendo al tamaño de este archivo, ya que esos datos serán almacenados en caché.

Su ÚNICA solución es no enviar contenido / datos de archivos a través de PHP y simplemente redirigir al usuario a una URL de descarga en el sistema de archivos.

Use un enlace simbólico generado y único, o una ubicación oculta.

No puede usar $ datos con datos de archivos completos dentro de él. Pruebe pasar a esta función, no al contenido del archivo, solo su ruta. Luego envíe todos los encabezados una vez y después de eso lea parte de este archivo usando fread (), repita ese fragmento, llame a flush () y repita. Si se enviará otro encabezado mientras tanto, finalmente la transferencia se dañará.

Haga un enlace simbólico entre el archivo grande y la raíz de su documento (suponiendo que no sea un archivo autorizado), luego permita que Apache lo maneje. (De esa forma puedes aceptar rangos de bytes también)

Agregue su ini_set antes de ini_set SESSION_START();