上传HTTP进度跟踪

我有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(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)或者我可以拒绝缓冲(HttpWebRequest的AllowWriteStreamBUffering不起作用)?

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(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下发生的(不是关于x64或x86)。 当我运行我的代码uder XP时,一切都运行良好,进度绝对正确

自问题发布以来已经超过一年了,但我认为我的post对某人有用。

我在显示进度方面遇到了同样的问题,它的行为与您描述的完全相同。 所以我决定使用HttpClient来正确显示上传进度。 然后我遇到了有趣的bug – 当我让Fiddler启动时,HttpClient开始以意想不到的方式显示其上传进度,就像上面的WebClient / HttpWebRequest一样,所以我想也许这就是为什么WebClient显示上传进度不正确的问题(我想我有它推出)。 所以我再次尝试使用WebClient(没有启动类似fiddler的应用程序)并且一切正常,上传进度具有正确的值。 我已经在几台带有win7和XP的PC上进行了测试,并且在所有情况下都取得了正确的进展。

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

这个讨论批准了它:

除非fiddler正在运行,否则HttpWebRequest不起作用

您可以使用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 (ie 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); } } 

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

这个至少有一天困扰我。 我已经开始使用WebClient.UploadFileAsync ,接下来尝试了HttpClientProgressMessageHandler ,然后为HttpClient API推出了自己的HttpContent 。 这些方法都不起作用(对我而言)。

看来HttpWebRequest位于大多数(全部?).NET Http抽象的底部,如WebClientHttpClient ,默认情况下会缓冲请求和响应流,我通过在ILSpy中查看它来确认。

正如其他人所指出的那样,您可以使您的请求以某种方式使用分块编码,这将有效地禁用缓冲请求流,但仍然无法修复进度报告。

我发现有必要在我发送的每个块之后刷新请求流以准确反映发送进度,否则您的数据将被简单地缓冲到管道的下一步(可能在NetworkStream或OS中的某个地方,没有校验)。 下面的示例代码对我有用,并且在从HttpWebResponse转换回HttpResponseMessage(您可能不需要,YMMV)时也做了一个简约的工作。

 public async Task UploadFileAsync( string uploadUrl, string absoluteFilePath, Action 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; } 

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

在第一个例子中,我认为你的进度条显示你从磁盘上的文件写入流的速度有多快 – 而不是实际的上传进度(这就是为什么这一切都发生在100%真正快速然后上传突变*)。

我可能错了^^并且没有WPF经验,但是我已经将大量文件从Silverlight上传到WCF,并且那里使用的模型(正如你所做的那样)将文件分解为块。 发送每个块。 当你从服务器得到响应时(“块26收到确定”),真的更新进度条,你不能(或不应该)更新进度条,除非你/知道/块x做了 – 并且知道这个问题的好方法是,如果服务器说它得到了它。

*我希望我能在5分钟内上传400Mb。 我会整天带我去……

我有同样的问题。 我花了很多时间解决了以下问题:防病毒AVAST。 当我关闭它时,我的程序完美运行…