上传HTTP进度跟踪

问题描述:

我有WPF应用程序我正在将该帖子文件写入其中一个社交网络。 上传本身工作得很好,但我想提供一些指示,我与上传有多远。上传HTTP进度跟踪

我试图一堆方法可以做到这一点:

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)Web客户端方式(更短):

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分钟 - 文件是400MB)。

所以我认为来自文件的数据会被缓冲到某个位置,而且我正在追踪不上传,而是缓冲数据。然后必须等到它上传。

我的问题是:

1)是否有任何方法来跟踪实际上传数据? Stream.Write()或Flush()(我在某处读取的方法不适用于NetworkStream)的方法直到它从服务器收到TCP数据包收到的确认后才会返回。

2)或者我可否拒绝缓冲(AllowWriteStreamBUffering为HttpWebRequest不起作用)?

3)是否有意义进一步“下”并尝试使用套接字?

更新:

为了避免在显示在UI前进路上的任何疑虑,我重写了代码日志文件。 所以,这里是代码:

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! 

,你可以看到程序认为其上传〜400MB约2秒钟。 7分钟后文件实际上传,我收到回复。

再次更新:

似乎这个在Windows 7下正在发生的事情(不舒尔约64或86)。 当我运行我的代码uder XP时,一切正常,进度显示绝对正确

这比今年以来这个问题被张贴更多,但我觉得我的帖子可能是有用的给某人。

我有显示进度同样的问题,像你描述它的表现完全一样。所以我决定使用正确显示上传进度的HttpClient。然后我遇到了有趣的bug - 当我推出Fiddler时HttpClient开始以意想不到的方式显示上传进度,就像在上面的WebClient/HttpWebRequest中一样,所以我认为这可能是WebClient显示上传进度不正确的一个问题(我想我有它启动)。所以我再次尝试使用WebClient(没有类似于应用程序的应用程序),并且所有的工作都是应该的,上传过程具有正确的值。我用win7和XP在几台PC上进行了测试,并且在所有情况下都能正确显示进度。

所以,我认为像小提琴手(很可能不仅是一个小提琴手)这样的程序对Web客户端和其他.NET类是如何显示上传进度有些影响。

讨论批准它:

HttpWebRequest doesn't work except when fiddler is running

+0

好。这似乎是合理的。我真的可以用打开的提琴手做测试。 我会再次检查,当我回家,thx! – Lumen

+0

我认为fiddler设置了一些系统钩子,使得所有的.net网络类都像他们不应该那样工作。 – Vlad

+0

好吧,据我所知,Fiddler是一个HTTP代理,所以它将拥有它自己的缓冲区,并在上传主机发送到目标服务器之前确认数据包。在启用Fiddler的情况下下载文件时会出现相反的情况(进度长时间保持0%,然后在下载文件从代理流向发起请求的主机时快速跳至100%)。 –

快速猜测,您正在UI线程上运行此代码。您需要在新线程上运行上传内容。 在这一点上,你有2个选项。 1)您在UI线程上运行计时器并更新UI。 2)使用Invoke(因为无法从另一个线程访问UI)调用更新UI来更新UI。

+0

没有,所有这些东西是在它自己的线程运行。这是WPF绑定和UI更新,因为我更改了sop.prct属性 – Lumen

+0

我以不同的方式观察了进度,在调试模式中逐步包含。例如,使用TcpClient 99%的时间获取这一行:'respStr = reader.ReadLine();'而其他一切运行不到一分钟 – Lumen

+0

我看不到(我是盲人或在其他地方定义)fileData byte []数组有多大。不管怎样,它不应该比16 - 256kb更糟。 – Ivar

在第一个例子,我认为你的进度条显示你写的速度有多快成从磁盘上的文件数据流 - 而不是实际的上传进度(这就是为什么这一切真的很快,然后上传一班班就刚好100% *)。

我可能是错的^^并且没有WPF的经验,但我从Silverlight的上传大量文件,WCF和使用的模式存在(因为你这样做),打破了文件成块。发送每个块。当你从服务器得到响应(“block 26 received ok”),更新进度条为真,你不能(或不应该)更新进度条,除非你知道/ block x做了它 - 并且知道这个好方法就是如果服务器说明了它。

*我希望我能在5分钟内上传400Mb。需要我整天...

+0

不关注WPF,它只负责接口。 我也认为它显示了写入缓冲区的进度,缓冲区随后将数据发送到网络级别的服务器。所以我需要一些事件,只有当部分数据实际发送到服务器或方法发生数据发送时才会返回。 不幸的是,服务器只接受一个大的http请求,并且我无法在http级别 – Lumen

您可以使用WebClientUploadFile来上传文件,而不是使用写入文件作为文件流。为了跟踪接收和上传数据的百分比,您可以使用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 UI。

上传进度改变事件做更多的通常被称为如果您对事件回调做任何IO那么会放缓下载进度。最好不要偶尔更新,例如以下代码只更新了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); 
     } 
    } 
+0

处分解文件感谢您的关注,我明天将尝试您的代码。但在我看来,与我上面描述的方法2)相比,没有什么区别。 – Lumen

+0

是的,请参阅我最近完成的更改。代码的底部。限制进度更改回调将提高性能(当你的回调有一些IO相关的东西)。 –

+0

问题依然存在:当我试图上传400MB文件时,进度条在10秒内从0变为50%(不能上传得太快),然后冻结5分钟,然后(当文件实际上上传)进度从50秒减少到100秒以内。 – Lumen

我的建议是使用新的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) 
      { 

      } 
     }); 
    } 

我有同样的问题。我花了很多时间并解决了这个问题,如下所示: Antivirus AVAST。当我把它关掉我的计划完美的作品...

这其中有被窃听我至少一天。我已经开始使用WebClient.UploadFileAsync,接下来尝试ProgressMessageHandlerHttpClient然后推出我自己的HttpContentHttpClient API。这些方法都无效(对我而言)。

看来HttpWebRequest,其在底部坐在最(所有?).NET的Http抽象像WebClientHttpClient,缓冲默认情况下请求和响应流,我证实了在ILSpy看着它。

正如其他人所指出的那样,你可以让你的请求使用分块编码这种或那种方式,这将有效地禁用缓存请求流,但仍这是不会解决的进展报告。

我发现为了准确地反映发送进度,我需要在每个发送块之后刷新请求流,否则您的数据将简单地缓冲到管道的下一级(可能位于NetworkStream或OS的某处,没有检查)。下面的代码示例适用于我,并且在从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; 
    }