2011-11-18 160 views
15

我有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時,一切正常,進度顯示絕對正確

回答

4

這比今年以來這個問題被張貼更多,但我覺得我的帖子可能是有用的給某人。

我有顯示進度同樣的問題,像你描述它的表現完全一樣。所以我決定使用正確顯示上傳進度的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%)。 –

0

快速猜測,您正在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

0

在第一個例子,我認爲你的進度條顯示你寫的速度有多快成從磁盤上的文件數據流 - 而不是實際的上傳進度(這就是爲什麼這一切真的很快,然後上傳一班班就剛好100% *)。

我可能是錯的^^並且沒有WPF的經驗,但我從Silverlight的上傳大量文件,WCF和使用的模式存在(因爲你這樣做),打破了文件成塊。發送每個塊。當你從服務器得到響應(「block 26 received ok」),更新進度條爲真,你不能(或不應該)更新進度條,除非你知道/ block x做了它 - 並且知道這個好方法就是如果服務器說明了它。

*我希望我能在5分鐘內上傳400Mb。需要我整天...

+0

不關注WPF,它只負責接口。 我也認爲它顯示了寫入緩衝區的進度,緩衝區隨後將數據發送到網絡級別的服務器。所以我需要一些事件,只有當部分數據實際發送到服務器或方法發生數據發送時纔會返回。 不幸的是,服務器只接受一個大的http請求,並且我無法在http級別 – Lumen

3

您可以使用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

1

我的建議是使用新的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

我有同樣的問題。我花了很多時間並解決了這個問題,如下所示: Antivirus AVAST。當我把它關掉我的計劃完美的作品...

1

這其中有被竊聽我至少一天。我已經開始使用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; 
    }