Как заставить загрузку больших файлов, не используя слишком много памяти? - PullRequest
9 голосов
/ 01 июня 2011

Я пытаюсь обслуживать большие zip-файлы для пользователей. Когда есть 2 одновременных соединения, серверу не хватает памяти (RAM). Я увеличил объем памяти с 300 МБ до 4 ГБ (Dreamhost VPS), а затем все заработало.

Мне нужно разрешить более двух одновременных подключений. Фактические 4 Гбайт позволили бы что-то вроде 20 одновременных подключений (слишком плохо).

Что ж, текущий код, который я использую, нуждается в удвоенной памяти, чем фактический размер файла Это очень плохо. Я хочу что-то вроде "потокового" файла для пользователя. Так что я бы выделил не больше, чем кусок, обслуживаемый пользователям.

Следующий код - это код, который я использую в CodeIgniter (PHP Framework):

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;

Функция force_download работает следующим образом (вспомогательная функция CodeIgniter по умолчанию):

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);
}

Я пробовал некоторые коды на основе чанков, которые я нашел в Google, но файл всегда доставлялся поврежденным. Возможно из-за плохого кода.

Может ли кто-нибудь мне помочь?

Ответы [ 5 ]

3 голосов
/ 01 июня 2011

Вы отправляете содержимое ($ data) этого файла через PHP?

Если это так, каждый процесс Apache, обрабатывающий это, в конечном итоге будет увеличиваться до размера этого файла, поскольку эти данные будут кэшироваться..

Ваше ЕДИНСТВЕННОЕ решение состоит в том, чтобы не отправлять содержимое файла / данные через PHP и просто перенаправить пользователя на URL-адрес загрузки в файловой системе.

Использовать сгенерированную и уникальную символическую ссылку или скрытое местоположение.

3 голосов
/ 01 июня 2011

Есть некоторые идеи в этой теме .Я не знаю, сохранит ли метод readfile () память, но это звучит многообещающе.

0 голосов
/ 07 июля 2011

Добавьте ваш ini_set до SESSION_START();

0 голосов
/ 01 июня 2011

Символическая ссылка большого файла на корневой каталог вашего документа (при условии, что это не авторизованный файл), а затем пусть Apache обработает его. (Таким образом, вы также можете принимать байтовые диапазоны)

0 голосов
/ 01 июня 2011

Вы не можете использовать $ data с целыми данными файла внутри него.Попробуйте передать в эту функцию не содержимое файла, а только его путь.Затем отправьте все заголовки один раз, а затем прочитайте часть этого файла с помощью fread (), отобразите этот фрагмент, вызовите flush () и повторите.Если в это время будет отправлен какой-либо другой заголовок, в конечном итоге передача будет повреждена.

...