2013-01-05 58 views
2

好吧,我正在處理我的文件傳輸服務,並且可以使用WCF流傳輸文件。我獲得了很好的速度,並且我最終能夠獲得良好的簡歷支持,因爲我在流式傳輸之前將文件分塊成小塊。如何在執行流操作時實現更好的粒度?

但是,當涉及到消息流式傳輸和寫入時測量詳細傳輸速度時,我遇到了服務器端傳輸和客戶端接收問題。

以下是文件分塊的代碼,每次需要向服務器發送另一個塊時,服務都會調用該文件。

public byte[] NextChunk() 
    { 
     if (MoreChunks) // If there are more chunks, procede with the next chunking operation, otherwise throw an exception. 
     { 
      byte[] buffer; 
      using (BinaryReader reader = new BinaryReader(File.OpenRead(FilePath))) 
      { 
       reader.BaseStream.Position = currentPosition; 
       buffer = reader.ReadBytes((int)MaximumChunkSize); 
      } 
      currentPosition += buffer.LongLength; // Sets the stream position to be used for the next call. 

      return buffer; 
     } 
     else 
      throw new InvalidOperationException("The last chunk of the file has already been returned."); 

在上文中,我基本上寫基於我使用的塊大小的緩衝液(在此情況下它是2MB,我發現具有相比更大或更小的塊大小的最佳傳輸速度)。然後,我會做一些工作來記住我離開的位置,然後返回緩衝區。

以下代碼是服務器端的工作。

public FileMessage ReceiveFile() 
    { 
     if (!transferSpeedTimer.Enabled) 
      transferSpeedTimer.Start(); 

     byte[] buffer = chunkedFile.NextChunk(); 

     FileMessage message = new FileMessage(); 
     message.FileMetaData = new FileMetaData(chunkedFile.MoreChunks, buffer.LongLength); 
     message.ChunkData = new MemoryStream(buffer); 

     if (!chunkedFile.MoreChunks) 
     { 
      OnTransferComplete(this, EventArgs.Empty); 

      Timer timer = new Timer(20000f); 
      timer.Elapsed += (sender, e) => 
      { 
       StopSession(); 
       timer.Stop(); 
      }; 
      timer.Start(); 
     } 

     //This needs to be more granular. This method is called too infrequently for a fast and accurate enough progress of the file transfer to be determined. 
     TotalBytesTransferred += buffer.LongLength; 

     return message; 
    } 

在這種方法中,它是由客戶端在WCF電話叫,我得到了一個塊信息,創建我的消息,請與定時器一點點,一旦轉讓完成,停止我的會議和更新傳輸速度。在我返回消息之前不久,我用緩衝區的長度增加了我的TotalBytesTransferred,緩衝區的長度用於幫助我計算傳輸速度。

問題是,這需要一段時間才能將文件流式傳輸到客戶端,因此我得到的速度是錯誤的。我試圖在這裏瞄準的是對TotalBytesTransferred變量進行更細緻的修改,以便更好地表示在任何給定時間向客戶端發送了多少數據。

現在,對於客戶端代碼,它使用完全不同的計算傳輸速度的方法。

if (Role == FileTransferItem.FileTransferRole.Receiver) 
     { 
      hostChannel = channelFactory.CreateChannel(); 
      ((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0); 

      bool moreChunks = true; 
      long bytesPreviousPosition = 0; 

      using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath))) 
      { 
       writer.BaseStream.SetLength(0); 

       transferSpeedTimer.Elapsed += ((sender, e) => 
       { 
        transferSpeed = writer.BaseStream.Position - bytesPreviousPosition; 
        bytesPreviousPosition = writer.BaseStream.Position; 
       }); 
       transferSpeedTimer.Start(); 

       while (moreChunks) 
       { 
        FileMessage message = hostChannel.ReceiveFile(); 
        moreChunks = message.FileMetaData.MoreChunks; 

        writer.BaseStream.Position = filePosition; 

        // This is golden, but I need to extrapolate it out and do the stream copy myself so I can do calculations on a per byte basis. 
        message.ChunkData.CopyTo(writer.BaseStream); 

        filePosition += message.FileMetaData.ChunkLength; 

        // TODO This needs to be more granular 
        TotalBytesTransferred += message.FileMetaData.ChunkLength; 
       } 

       OnTransferComplete(this, EventArgs.Empty); 
      } 
     } 
    else 
     { 
      transferSpeedTimer.Elapsed += ((sender, e) => 
      { 
       totalElapsedSeconds += (int)transferSpeedTimer.Interval; 
       transferSpeed = TotalBytesTransferred/totalElapsedSeconds; 
      }); 
      transferSpeedTimer.Start(); 

      host.Open(); 
     } 

在這裏,我TotalBytesTransferred也是基於的塊進來的長度。我知道我能得到一個更精確的計算,如果我做的流寫我自己,而不是使用CopyTo的流,但我我不完全確定如何最好地解決這個問題。

有人可以幫我嗎?在這個班級之外,我有另一個班級輪詢TransferSpeed的財產,因爲它在內部更新。

我道歉如果我張貼了太多的代碼,但我不知道要發佈什麼,什麼不是。

編輯:我意識到,至少在服務器端的實現中,我可以通過讀取流的返回消息值的位置來更詳細地讀取已傳輸多少字節的方式。但是,我不知道這樣做的方式,以確保我的數量絕對完整。我想過可能會使用定時器並在流傳輸時輪詢位置,但接下來可能會進行調用,並且很快就會失去同步。

如何從返回的流中輪詢數據並立即知道流何時完成,以便我可以快速將剩餘流的剩餘部分累加到字節數中?

回答

0

好吧,我發現什麼似乎是理想的我。我不知道它是否完美,但對我的需求來說非常合適。

Server方面,我們有這個代碼來完成文件傳輸的工作。 chunkedFile類顯然是分塊,但這是將信息發送到Client的代碼。

public FileMessage ReceiveFile() 
    { 
     byte[] buffer = chunkedFile.NextChunk(); 

     FileMessage message = new FileMessage(); 
     message.FileMetaData = new FileMetaData(chunkedFile.MoreChunks, buffer.LongLength, chunkedFile.CurrentPosition); 
     message.ChunkData = new MemoryStream(buffer); 

     TotalBytesTransferred = chunkedFile.CurrentPosition; 
     UpdateTotalBytesTransferred(message); 

     if (!chunkedFile.MoreChunks) 
     { 
      OnTransferComplete(this, EventArgs.Empty); 

      Timer timer = new Timer(20000f); 
      timer.Elapsed += (sender, e) => 
      { 
       StopSession(); 
       timer.Stop(); 
      }; 
      timer.Start(); 
     } 

     return message; 
    } 

客戶基本上調用此代碼,並且服務器繼續得到一個新的塊,把它放在一個流的基礎上,chunkedFile的位置更新TotalBytesTransferred(它記錄了底層文件系統文件用於繪製數據)。我將稍後展示方法UpdateTotalBytesTransferred(message),因爲這是服務器和客戶端的所有代碼駐留的地方,以實現TotalBytesTransferred的更精細輪詢。

接下來是客戶端工作。

  hostChannel = channelFactory.CreateChannel(); 
      ((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0); 

      bool moreChunks = true; 

      using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath))) 
      { 
       writer.BaseStream.SetLength(0); 

       while (moreChunks) 
       { 
        FileMessage message = hostChannel.ReceiveFile(); 
        moreChunks = message.FileMetaData.MoreChunks; 

        UpdateTotalBytesTransferred(message); 

        writer.BaseStream.Position = filePosition; 

        message.ChunkData.CopyTo(writer.BaseStream); 
        TotalBytesTransferred = message.FileMetaData.FilePosition; 

        filePosition += message.FileMetaData.ChunkLength; 
       } 

       OnTransferComplete(this, EventArgs.Empty); 
      } 

此代碼非常簡單。它調用主機來獲取文件流,並且還利用了UpdateTotalBytesTransferred(message)方法。它會做一些工作來記住正在寫入的底層文件的位置,並將流複製到該文件,同時在完成後更新TotalBytesTransferred

我所追求的粒度的方法與UpdateTotalBytesTransferred方法如下。它對於服務器和客戶端都完全相同。

private void UpdateTotalBytesTransferred(FileMessage message) 
    { 
     long previousStreamPosition = 0; 
     long totalBytesTransferredShouldBe = TotalBytesTransferred + message.FileMetaData.ChunkLength; 

     Timer timer = new Timer(500f); 

     timer.Elapsed += (sender, e) => 
     { 
      if (TotalBytesTransferred + (message.ChunkData.Position - previousStreamPosition) < totalBytesTransferredShouldBe) 
      { 
       TotalBytesTransferred += message.ChunkData.Position - previousStreamPosition; 
       previousStreamPosition = message.ChunkData.Position; 
      } 
      else 
      { 
       timer.Stop(); 
       timer.Dispose(); 
      } 
     }; 

     timer.Start(); 
    } 

這是做什麼是在FileMessage中,這基本上只是一個流和一些關於文件本身的信息。它有一個變量previousStreamPosition來記住它輪詢基礎流時的最後一個位置。它還根據已經傳輸多少個字節加上流的總長度,用totalBytesTransferredShouldBe進行簡單的計算。

最後,創建並執行一個計時器,該計時器在每個滴答檢查時檢查是否需要遞增TotalBytesTransferred。如果它不再更新它(基本上已到達流的末尾),它會停止並處理定時器。

這一切都允許我讀取已傳輸多少字節的非常小的讀數,這使我能夠以更流暢的方式更好地計算總進度,從而更準確地衡量所達到的文件傳輸速度。

相關問題