Загрузка отслеживания прогресса HTTP - PullRequest
15 голосов
/ 18 ноября 2011

У меня есть приложение WPF, которое я пишу и публикует файлы в одной из социальных сетей.Сама загрузка работает нормально, но я хотел бы дать некоторое представление о том, как далеко я продвинулась с загрузкой.

Я попробовал несколько способов сделать это:

1) HttpWebRequestМетод .GetStream:

using (
 var FS = File.Open(
  localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
    request.Method = "POST";
    request.ProtocolVersion = HttpVersion.Version11;
    request.ContentType = "multipart/form-data; boundary=--AaB03x";
    //predata and postdata is two byte[] arrays, that contains
    //strings for MIME file upload (defined above and is not important)
    request.ContentLength = predata.Length + FS.Length + postdata.Length;
    request.AllowWriteStreamBuffering = false;
    using (var reqStream = request.GetRequestStream())
    {
        reqStream.Write(predata, 0, predata.Length);
        int bytesRead = 0;
        int totalRead = 0;
        do
        {
            bytesRead = FS.Read(fileData, 0, MaxContentSize);
            totalRead += bytesRead;
            reqStream.Write(fileData, 0, bytesRead);
            reqStream.Flush(); //trying with and without this
            //this part will show progress in percents
            sop.prct = (int) ((100*totalRead)/len);
        } while (bytesRead > 0);
        reqStream.Write(postdata, 0, postdata.Length);
    }
    HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
    using (var respStream = responce.GetResponseStream())
    {
        //do things
    }
}

2) Способ WebClient (намного короче):

void UploadFile (url, localFilePath)
{
    ...
    WebClient client = new WebClient();
    client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadPartDone);
    client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadComplete);
    client.UploadFileAsync(new Uri(url), localFilePath);
    done.WaitOne();

    //do things with responce, received from UploadComplete
    JavaScriptSerializer jssSer = new JavaScriptSerializer();
    return jssSer.Deserialize<UniversalJSONAnswer>(utf8.GetString(UploadFileResponce));
    //so on...
    ...
}

void UploadComplete(object sender, UploadFileCompletedEventArgs e)
{
    UploadFileResponce=e.Result;
    done.Set();
}

void UploadPartDone(object sender, UploadProgressChangedEventArgs e)
{
    //this part expected to show progress
    sop.prct=(int)(100*e.BytesSent/e.TotalBytesToSend);
}

3) Четный способ TcpClient:

using (
 var FS = File.Open(
  localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    long totalRead = 0;
    using (var client = new TcpClient(urli.Host, urli.Port))
    {
        using (var clearstream = client.GetStream())
        {
            using (var writer = new StreamWriter(clearstream))
            using (var reader = new StreamReader(clearstream))
            {
                //set progress to 0
                sop.prct = 0;
                // Send request headers
                writer.WriteLine("POST " + urli.AbsoluteUri + " HTTP/1.1");
                writer.WriteLine("Content-Type: multipart/form-data; boundary=--AaB03x");
                writer.WriteLine("Host: " + urli.Host);
                writer.WriteLine("Content-Length: " + (predata.Length + len + postdata.Length).ToString());
                writer.WriteLine();
                //some data for MIME
                writer.Write(utf8.GetString(predata));
                writer.Flush();
                int bytesRead;
                do
                {
                    bytesRead = FS.Read(fileData, 0, MaxContentSize);
                    totalRead += bytesRead;
                    writer.BaseStream.Write(fileData, 0, bytesRead);
                    writer.BaseStream.Flush();
                    sop.prct = (int) ((100*totalRead)/len);
                } while (bytesRead > 0)
                writer.Write(utf8.GetString(postdata));
                writer.Flush();
                //read line of response and do other thigs...
                respStr = reader.ReadLine();
                ...
            }
        }
    }
}

Во всех случаяхфайл был успешно отправлен на сервер.Но всегда прогресс выглядит следующим образом: в течение нескольких секунд он запускается с 0 до 100, а затем ждет, пока файл действительно будет загружен (около 5 минут - размер файла 400 МБ).

Так что я думаю, что данные из файла буферизуютсяи я отслеживаю не загрузку, а буферизацию данныхИ затем должен дождаться загрузки.

Мои вопросы:

1) Есть ли способ отследить фактическую загрузку данных?То, что метод Stream.Write () или Flush () (который, как я где-то читал, не работает для NetworkStream) не возвращался до тех пор, пока не получит подтверждение от сервера о получении пакетов TCP.

2) Илиможно отрицать буферизацию (AllowWriteStreamBUffering для HttpWebRequest не работает)?

3) И имеет ли смысл идти дальше "вниз" и пытаться с сокетами?

updated:

Чтобы избежать каких-либо сомнений в способе отображения прогресса в пользовательском интерфейсе, я переписал код для регистрации файла.Итак, вот код:

using (var LogStream=File.Open("C:\\123.txt",FileMode.Create,FileAccess.Write,FileShare.Read))
using (var LogWriter=new StreamWriter(LogStream))
using (var FS = File.Open(localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
    request.Timeout = 7200000; //2 hour timeout
    request.Method = "POST";
    request.ProtocolVersion = HttpVersion.Version11;
    request.ContentType = "multipart/form-data; boundary=--AaB03x";
    //predata and postdata is two byte[] arrays, that contains
    //strings for MIME file upload (defined above and is not important)
    request.ContentLength = predata.Length + FS.Length + postdata.Length;
    request.AllowWriteStreamBuffering = false;
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " Start write into request stream. ");
    using (var reqStream = request.GetRequestStream())
    {
        reqStream.Write(predata, 0, predata.Length);
        int bytesRead = 0;
        int totalRead = 0;
        do
        {
            bytesRead = FS.Read(fileData, 0, MaxContentSize);
            totalRead += bytesRead;
            reqStream.Write(fileData, 0, bytesRead);
            reqStream.Flush(); //trying with and without this
            //sop.prct = (int) ((100*totalRead)/len); //this part will show progress in percents
            LogWriter.WriteLine(DateTime.Now.ToString("o") + " totalRead= " + totalRead.ToString() + " / " + len.ToString());
        } while (bytesRead > 0);
        reqStream.Write(postdata, 0, postdata.Length);
    }
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " All sent!!! Waiting for responce... ");
    LogWriter.Flush();
    HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " Responce received! ");
    using (var respStream = responce.GetResponseStream())
    {
        if (respStream == null) return null;
        using (var streamReader = new StreamReader(respStream))
        {
            string resp = streamReader.ReadToEnd();
            JavaScriptSerializer jssSer = new JavaScriptSerializer();
            return jssSer.Deserialize<UniversalJSONAnswer>(resp);
        }
    }
}

и вот результат (я обрезал середину):

2011-11-19T22:00:54.5964408+04:00 Start write into request stream. 
2011-11-19T22:00:54.6404433+04:00 totalRead= 1048576 / 410746880
2011-11-19T22:00:54.6424434+04:00 totalRead= 2097152 / 410746880
2011-11-19T22:00:54.6434435+04:00 totalRead= 3145728 / 410746880
2011-11-19T22:00:54.6454436+04:00 totalRead= 4194304 / 410746880
2011-11-19T22:00:54.6464437+04:00 totalRead= 5242880 / 410746880
2011-11-19T22:00:54.6494438+04:00 totalRead= 6291456 / 410746880
.......    
2011-11-19T22:00:55.3434835+04:00 totalRead= 408944640 / 410746880
2011-11-19T22:00:55.3434835+04:00 totalRead= 409993216 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 All sent!!! Waiting for responce... 
2011-11-19T22:07:23.0616597+04:00 Responce received! 

, как вы видите, программа думает, что она загрузила ~ 400 МБ в течение примерно 2 секунд,И через 7 минут файл действительно загружается, и я получаю ответ.

обновляется снова:

Кажется, что это происходит в Windows 7 (не уверен насчет x64 или x86).Когда я запускаю свой код под uder XP, все работает отлично и прогресс отображается абсолютно корректно

Ответы [ 7 ]

4 голосов
/ 07 декабря 2012

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

У меня была такая же проблема с отображением прогресса, и она вела себя точно так же, как вы описали.Поэтому я решил использовать HttpClient, который правильно показывает прогресс загрузки.Затем я столкнулся с интересной ошибкой - когда я запустил Fiddler, HttpClient начал показывать процесс загрузки неожиданным образом, как в WebClient / HttpWebRequest выше, поэтому я подумал, что, возможно, именно из-за того, что WebClient показал некорректную загрузку (я думаю, чтооно запущено).Поэтому я попробовал снова с WebClient (без запуска приложений, похожих на fiddler), и все работает как надо, прогресс загрузки имеет правильные значения.Я проверил на нескольких ПК с win7 и XP, и во всех случаях прогресс показывал правильно.

Итак, я думаю, что такая программа, как Fiddler (вероятно, не только fiddler), имеет некоторое влияние на то, как WebClient и другие классы .net показывают ход загрузки.

это обсуждение подтверждает:

HttpWebRequest не работает, кроме случаев, когда запущен fiddler

3 голосов
/ 13 декабря 2011

Вы можете использовать WebClient UploadFile для загрузки файла, вместо использования записи файла в качестве потока файла.Для отслеживания процента полученных и загруженных данных вы можете использовать UploadFileAsyn и подписаться на его события.

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

class FileUploader : IDisposable
{
    private readonly WebClient _client;
    private readonly Uri _address;
    private readonly string _filePath;
    private bool _uploadCompleted;
    private bool _uploadStarted;
    private bool _status;

    public FileUploader(string address, string filePath)
    {
        _client = new WebClient();
        _address = new Uri(address);
        _filePath = filePath;
        _client.UploadProgressChanged += FileUploadProgressChanged;
        _client.UploadFileCompleted += FileUploadFileCompleted;
    }

    private void FileUploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
    {
        _status = (e.Cancelled || e.Error == null) ? false : true;
        _uploadCompleted = true;
    }

    private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
    {
        if(e.ProgressPercentage % 10 == 0)
        {
            //This writes the pecentage data uploaded and downloaded
            Console.WriteLine("Send: {0}, Received: {1}", e.BytesSent, e.BytesReceived);
            //You can have a delegate or a call back to update your UI about the percentage uploaded
            //If you don't have the condition (i.e e.ProgressPercentage % 10 == 0 )for the pecentage of the process 
            //the callback will slow you upload process down
        }
    }

    public bool Upload()
    {

        if (!_uploadStarted)
        {
            _uploadStarted = true;
            _client.UploadFileAsync(_address, _filePath);
        }
        while (!_uploadCompleted)
        {
            Thread.Sleep(1000);
        }
        return _status;
    }

    public void Dispose()
    {
        _client.Dispose();
    }
}

Код клиента:

            using (FileUploader uploader = new FileUploader("http://www.google.com", @"C:\test.txt"))
        {
            uploader.Upload();
        }

Вы можете зарегистрировать пользовательский обратный вызов (может быть делегатом) в обработчике FileUploadProgressChanged, чтобы обновить пользовательский интерфейс WPF.

Событие загрузки с измененным прогрессом вызывается чаще, если ваш обратный вызов для события выполняет какой-либо ввод-вывод, что замедлит процесс загрузки.Лучше нечастое обновление, например, следующее обновление кода только на 10% больше.

    private int _percentageDownloaded;

    private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
    {
        if (e.ProgressPercentage % 10 == 0 && e.ProgressPercentage > _percentageDownloaded)
        {

            _percentageDownloaded = e.ProgressPercentage;
            //Any callback instead of printline
            Console.WriteLine("Send: {0} Received: {1}", e.BytesSent, e.BytesReceived);
        }
    }
1 голос
/ 19 января 2015

Этот человек давал мне по крайней мере один день. Я начал с использования WebClient.UploadFileAsync, затем попробовал ProgressMessageHandler для HttpClient, затем свернул свой собственный HttpContent для HttpClient API. Ни один из этих подходов не сработал (для меня).

Похоже, HttpWebRequest, который находится в нижней части большинства (всех?) Абстракций .NET Http, таких как WebClient и HttpClient, буферизует поток запросов и ответов по умолчанию, что я подтвердил, посмотрев его в ILSpy.

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

Я обнаружил, что необходимо очищать поток запросов после каждого блока, который я отправляю, чтобы точно отразить ход отправки, иначе ваши данные будут просто буферизованы на один шаг дальше по конвейеру (возможно, где-то в NetworkStream или ОС, не проверял). Пример кода ниже работает для меня, а также выполняет минималистичную работу по переводу обратно из HttpWebResponse в HttpResponseMessage (который вам может не понадобиться, YMMV).

public async Task<HttpResponseMessage> UploadFileAsync( string uploadUrl, string absoluteFilePath, Action<int> progressPercentCallback )
    {
        var length = new FileInfo( absoluteFilePath ).Length;

        var request = new HttpWebRequest( new Uri(uploadUrl) ) {
            Method = "PUT",
            AllowWriteStreamBuffering = false,
            AllowReadStreamBuffering = false,
            ContentLength = length
        };

        const int chunkSize = 4096;
        var buffer = new byte[chunkSize];

        using (var req = await request.GetRequestStreamAsync())
        using (var readStream = File.OpenRead(absoluteFilePath))
        {
            progressPercentCallback(0);
            int read = 0;
            for (int i = 0; i < length; i += read)
            {
                read = await readStream.ReadAsync( buffer, 0, chunkSize );
                await req.WriteAsync( buffer, 0, read );
                await req.FlushAsync(); // flushing is required or else we jump to 100% very fast
                progressPercentCallback((int)(100.0 * i / length));
            }
            progressPercentCallback(100);
        }

        var response = (HttpWebResponse)await request.GetResponseAsync();
        var result = new HttpResponseMessage( response.StatusCode );
        result.Content = new StreamContent( response.GetResponseStream() );

        return result; 
    }
1 голос
/ 16 ноября 2012

Я предлагаю использовать новый класс HTTPClient (доступен в .NET 4.5). Поддерживает прогресс.

Эта статья очень помогла мне в этом: http://www.strathweb.com/2012/06/drag-and-drop-files-to-wpf-application-and-asynchronously-upload-to-asp-net-web-api/

Мой код для загрузки файла:

    private void HttpSendProgress(object sender, HttpProgressEventArgs e)
    {
        HttpRequestMessage request = sender as HttpRequestMessage;
        Console.WriteLine(e.BytesTransferred);
    }

    private void Window_Loaded_1(object sender, RoutedEventArgs e)
    {
        ProgressMessageHandler progress = new ProgressMessageHandler();
        progress.HttpSendProgress += new EventHandler<HttpProgressEventArgs>(HttpSendProgress);

        HttpRequestMessage message = new HttpRequestMessage();
        StreamContent streamContent = new StreamContent(new FileStream("e:\\somefile.zip", FileMode.Open));

        message.Method = HttpMethod.Put;
        message.Content = streamContent;
        message.RequestUri = new Uri("{Here your link}");

        var client = HttpClientFactory.Create(progress);

        client.SendAsync(message).ContinueWith(task =>
        {
            if (task.Result.IsSuccessStatusCode)
            { 

            }
        });
    }
0 голосов
/ 05 января 2014

У меня была такая же проблема.Я потратил много времени и решил проблему следующим образом: Антивирус AVAST.Когда я выключаю его, моя программа работает отлично ...

0 голосов
/ 18 ноября 2011

В первом примере я думаю, что ваш индикатор показывает, как быстро вы записываете в поток данные из файла на диске, а не фактический прогресс загрузки (именно поэтому все происходит на 100% быстрее, чем при загрузке) ).

Возможно, я ошибаюсь ^^ и у меня нет опыта работы с WPF, но я загрузил массивные файлы из Silverlight в WCF, и там используется модель (как и вы), чтобы разбить файл на блоки. Отправьте каждый блок. Когда вы получите ответ от сервера («блок 26 получен нормально»), обновите индикатор выполнения как действительно, вы не можете (или не должны) обновлять индикатор выполнения, если вы / знаете / этот блок x не сделал это - и хороший способ узнать это, если сервер говорит, что получил.

* Я хотел бы загрузить 400 МБ за 5 минут. Отнял бы меня весь день ...

0 голосов
/ 18 ноября 2011

По быстрому предположению, вы запускаете этот код в потоке интерфейса.Вам нужно запустить загрузку материала в новой теме.На данный момент у вас есть 2 варианта.1) Вы запускаете таймер в потоке пользовательского интерфейса и обновляете пользовательский интерфейс.2) Вы обновляете пользовательский интерфейс с помощью вызовов Invoke (поскольку вы не можете получить доступ к пользовательскому интерфейсу из другого потока) для обновления пользовательского интерфейса.

...