WebRequest не может загрузить большие файлы (~ 1 ГБ) должным образом
Я пытаюсь загрузить большой файл из общедоступного URL-адреса. Казалось, что все работает нормально, но 1/10 компьютеров, похоже, тайм-аут. Моя первоначальная попытка состояла в том, чтобы использовать WebClient.DownloadFileAsync
но поскольку он никогда не завершится, я вернулся к использованию WebRequest.Create
и прочитал потоки ответов напрямую.
Моя первая версия использования WebRequest.Create
обнаружила ту же проблему, что и WebClient.DownloadFileAsync
. Операция заканчивается, и файл не завершается.
В моей следующей версии добавлены повторы, если время загрузки. Вот это было странно. В конце концов загрузка заканчивается 1 повторением, чтобы закончить последние 7092 байта. Таким образом, файл загружается точно таким же размером, НО файл поврежден и отличается от исходного файла. Теперь я ожидаю, что коррупция будет в последних 7092 байтах, но это не так.
Используя BeyondCompare, я обнаружил, что в коррумпированном файле хватает 2 куска байтов, суммирующих до 7092 байта! Эти пропущенные байты находятся в 1CA49FF0
и 1E31F380
, путь до 1E31F380
времени загрузки и перезапускаются.
Что может быть здесь? Любые подсказки о том, как отслеживать эту проблему дальше?
Вот этот код.
public void DownloadFile(string sourceUri, string destinationPath)
{
//roughly based on: http://stackoverflow.com/info/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/info/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.
Ответы
Ответ 1
это метод, который я обычно использую, он до сих пор не подвел меня к тому, что вам нужно. Попытайтесь использовать мой код, чтобы немного поменять свое и посмотреть, поможет ли это.
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;
}
Ответ 2
Вы должны изменить настройки таймаута. Кажется, есть два возможных тайм-аута:
- Тайм-аут на стороне клиента - попробуйте изменить тайм-ауты в WebClient. Иногда я нахожусь для больших загрузок файлов, я должен это делать.
- Тайм-аут на стороне сервера - попробуйте изменить таймаут на сервере. Вы можете проверить, что это проблема с использованием другого клиента, например PostMan
Ответ 3
Для меня ваш метод чтения файла с помощью буферизации выглядит очень странно. Может быть, проблема в том, что вы делаете
while(bytesRead > 0)
Что, если по какой-то причине поток не возвращает какие-либо байты в какой-то момент, но он еще не завершен, он выйдет из цикла и никогда не вернется. Вы должны получить Content-Length и увеличивать переменную totalBytesReceived by bytesRead. Наконец, вы меняете цикл на
while(totalBytesReceived < ContentLength)
Ответ 4
Выделить размер буфера больше, чем ожидаемый размер файла.
byte [] byteBuffer = новый байт [65536];
так что, если размер файла 1GiB, вы выделяете буфер 1 GiB, а затем пытаетесь заполнить весь буфер за один вызов. Это заполнение может вернуть меньше байтов, но вы все равно выделили весь буфер. Обратите внимание, что максимальная длина одного массива в.NET - это 32-разрядное число, что означает, что даже если вы перекомпилируете свою программу на 64-битную и на самом деле достаточно памяти.