WebRequest не может правильно загрузить большие файлы (~ 1 ГБ)

Я пытаюсь загрузить большой файл с общедоступного URL-адреса. Сначала казалось, что он работает нормально, но 1/10 компьютеров, похоже, истекли. Моя первоначальная попытка состояла в том, чтобы использовать WebClient.DownloadFileAsync, но, поскольку она никогда не завершалась, я вернулся к использованию WebRequest.Create и напрямую читал потоки ответов.

Моя первая версия использования WebRequest.Create обнаружила ту же проблему, что и WebClient.DownloadFileAsync. Операция истекает, и файл не завершается.

В моей следующей версии добавлены повторные попытки, если время загрузки истекло. Вот были странности. Загрузка в конечном итоге заканчивается с 1 повторной попыткой, чтобы закончить последние 7092 байта. Таким образом, файл загружается точно такого же размера, НО файл поврежден и отличается от исходного файла. Теперь я ожидаю, что повреждение будет в последних 7092 байтах, но это не так.

Используя BeyondCompare, я обнаружил, что в поврежденном файле отсутствуют 2 фрагмента байтов, что в сумме составляет отсутствующие 7092 байта! Эти отсутствующие байты находятся в 1CA49FF0 и 1E31F380, задолго до истечения времени загрузки и перезапуска.

Что здесь может происходить? Любые подсказки о том, как отследить эту проблему дальше?

Вот код, о котором идет речь.

public void DownloadFile(string sourceUri, string destinationPath)
{
    //roughly based on: http://stackoverflow.com/questions/2269607/how-to-programmatically-download-a-large-file-in-c-sharp
    //not using WebClient.DownloadFileAsync as it seems to stall out on large files rarely for unknown reasons.

    using (var fileStream = File.Open(destinationPath, FileMode.Create, FileAccess.Write, FileShare.Read))
    {
        long totalBytesToReceive = 0;
        long totalBytesReceived = 0;
        int attemptCount = 0;
        bool isFinished = false;

        while (!isFinished)
        {
            attemptCount += 1;

            if (attemptCount > 10)
            {
                throw new InvalidOperationException("Too many attempts to download. Aborting.");
            }

            try
            {
                var request = (HttpWebRequest)WebRequest.Create(sourceUri);

                request.Proxy = null;//http://stackoverflow.com/questions/754333/why-is-this-webrequest-code-slow/935728#935728
                _log.AddInformation("Request #{0}.", attemptCount);

                //continue downloading from last attempt.
                if (totalBytesReceived != 0)
                {
                    _log.AddInformation("Request resuming with range: {0} , {1}", totalBytesReceived, totalBytesToReceive);
                    request.AddRange(totalBytesReceived, totalBytesToReceive);
                }

                using (var response = request.GetResponse())
                {
                    _log.AddInformation("Received response. ContentLength={0} , ContentType={1}", response.ContentLength, response.ContentType);

                    if (totalBytesToReceive == 0)
                    {
                        totalBytesToReceive = response.ContentLength;
                    }

                    using (var responseStream = response.GetResponseStream())
                    {
                        _log.AddInformation("Beginning read of response stream.");
                        var buffer = new byte[4096];
                        int bytesRead = responseStream.Read(buffer, 0, buffer.Length);
                        while (bytesRead > 0)
                        {
                            fileStream.Write(buffer, 0, bytesRead);
                            totalBytesReceived += bytesRead;
                            bytesRead = responseStream.Read(buffer, 0, buffer.Length);
                        }

                        _log.AddInformation("Finished read of response stream.");
                    }
                }

                _log.AddInformation("Finished downloading file.");
                isFinished = true;
            }
            catch (Exception ex)
            {
                _log.AddInformation("Response raised exception ({0}). {1}", ex.GetType(), ex.Message);
            }
        }
    }
}

Вот вывод журнала поврежденной загрузки:

Request #1.
Received response. ContentLength=939302925 , ContentType=application/zip
Beginning read of response stream.
Response raised exception (System.Net.WebException). The operation has timed out.
Request #2.
Request resuming with range: 939295833 , 939302925
Received response. ContentLength=7092 , ContentType=application/zip
Beginning read of response stream.
Finished read of response stream.
Finished downloading file.

person Spish    schedule 07.12.2012    source источник
comment
Я мог думать две вещи на макушке. а) Увеличьте время ожидания для больших файлов (если возможно) б) может ли ваша кодировка и декодирование данных быть поврежденными? У меня была эта проблема в другом проекте, который у меня когда-то был. Попробуйте закодировать его с помощью UTF-8   -  person Steven    schedule 07.12.2012
comment
Это не должно быть проблемой кодировки, это двоичный объект (zip-файл).   -  person Spish    schedule 07.12.2012
comment
Мне кажется, вы пытаетесь отладить ошибку сервера не на том конце провода.   -  person Hans Passant    schedule 08.12.2012
comment
Ганс, похоже, ты прав. Нам удалось воссоздать проблему (в поврежденных файлах отсутствуют байты), загрузив с помощью Chrome.   -  person Spish    schedule 08.12.2012
comment
+1 за решение совершенно не связанной проблемы, с которой я столкнулся, опубликовав ваш полный метод DownloadFile.   -  person cod3monk3y    schedule 06.02.2014
comment
Зачем ты вообще пытаешься? Просто отключите тайм-аут. Для длительных загрузок нет смысла устанавливать тайм-аут, вам не нужен тайм-аут.   -  person usr    schedule 15.12.2015


Ответы (4)


это метод, который я обычно использую, он до сих пор не подводил меня для того же типа загрузки, который вам нужен. Попробуйте использовать мой код, чтобы немного изменить свой, и посмотрите, поможет ли это.

if (!Directory.Exists(localFolder))
{
    Directory.CreateDirectory(localFolder);   
}


try
{
    HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(Path.Combine(uri, filename));
    httpRequest.Method = "GET";

    // if the URI doesn't exist, exception gets thrown here...
    using (HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse())
    {
        using (Stream responseStream = httpResponse.GetResponseStream())
        {
            using (FileStream localFileStream = 
                new FileStream(Path.Combine(localFolder, filename), FileMode.Create))
            {
                var buffer = new byte[4096];
                long totalBytesRead = 0;
                int bytesRead;

                while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    totalBytesRead += bytesRead;
                    localFileStream.Write(buffer, 0, bytesRead);
                }
            }
        }
    }
}
catch (Exception ex)
{        
    throw;
}
person Sizons    schedule 24.05.2016

Вы должны изменить настройки тайм-аута. Кажется, есть две возможные проблемы с тайм-аутом:

  • Тайм-аут на стороне клиента — попробуйте изменить тайм-аут в WebClient. Я считаю, что для загрузки больших файлов иногда мне нужно это сделать.
  • Тайм-аут на стороне сервера — попробуйте изменить тайм-аут на сервере. Вы можете подтвердить, что это проблема, используя другой клиент, например. Почтальон
person A X    schedule 01.01.2018

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

while(bytesRead > 0)

Что, если по какой-то причине поток не возвращает ни одного байта в какой-то момент, но он еще не закончил загрузку, тогда он выйдет из цикла и больше никогда не вернется. Вы должны получить Content-Length и увеличить переменную totalBytesReceived bytesRead. Наконец, вы меняете цикл на

while(totalBytesReceived < ContentLength)
person Johannes J.    schedule 02.02.2018

Выделите размер буфера больше, чем ожидаемый размер файла.

byte[] byteBuffer = новый байт[65536];

так что, если размер файла составляет 1 ГБ, вы выделяете буфер размером 1 ГБ, а затем пытаетесь заполнить весь буфер за один вызов. Это заполнение может вернуть меньше байтов, но вы все равно выделили весь буфер. Обратите внимание, что максимальная длина одного массива в .NET — это 32-разрядное число, что означает, что даже если вы перекомпилируете свою программу для 64-разрядной версии и на самом деле имеете достаточно доступной памяти.

person Mrunalini    schedule 13.04.2018