2012-01-04 201 views
2

我正在構建一個解決方案,其中WCF服務充當必須通過FTP協議(linux服務器)遠程訪問的FTP服務器和Windows客戶端應用程序之間的網關。該服務本身將託管在Windows IIS服務器上。使用WCF從遠程FTP服務器下載和流式傳輸文件

我基於對使用WCF流媒體文件通過HTTP的文章我的模型,但問題是:

我必須等待該文件的Windows服務器上之前首先straming下載到客戶端和可能是一個主要的性能問題。我想直接將文件從FTP服務器傳輸到客戶端,而無需先下載它。

這裏的代碼..

public class TransferService : ITransferService{ 
Starksoft.Net.Ftp.FtpClient ftp = new Starksoft.Net.Ftp.FtpClient(); 
public RemoteFileInfo DownloadFile(DownloadRequest request) 
{ 
    RemoteFileInfo result = new RemoteFileInfo(); 
    try 
    { 
     string filePath = System.IO.Path.Combine(@"C:\UploadFiles\ServerDownloadFiles", request.FileName); 
     System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath); 

     ftp = new Starksoft.Net.Ftp.FtpClient("127.0.0.1"); //remote ftp address 
     ftp.Open("user", "pass"); 

     // here is waiting for the file to get downloaded from ftp server 
     System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create, System.IO.FileAccess.Write); 

     ftp.GetFileAsync(request.FileName, stream, true); 

     stream.Close(); 
     stream.Dispose(); 

     // this will read and be streamed to client 
     System.IO.FileStream stream2 = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); 

     result.FileName = request.FileName; 
     result.Length = stream2.Length; 
     result.FileByteStream = stream2; 

    } 
    catch (Exception ex) 
    { 

    } 
    return result; 

} 

像這樣的客戶:

// start service client 
      FileTransferClient.TransferServiceClient client = new FileTransferClient.TransferServiceClient(); 

      LogText("Start"); 

      // kill target file, if already exists 
      string filePath = System.IO.Path.Combine("Download", textBox1.Text); 
      if (System.IO.File.Exists(filePath)) System.IO.File.Delete(filePath); 

      // get stream from server 
      System.IO.Stream inputStream; 
      string fileName = textBox1.Text; 
      long length = client.DownloadFile(ref fileName, out inputStream); 

      // write server stream to disk 
      using (System.IO.FileStream writeStream = new System.IO.FileStream(filePath, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write)) 
      { 
       int chunkSize = 2048; 
       byte[] buffer = new byte[chunkSize]; 

       do 
       { 
        // read bytes from input stream 
        int bytesRead = inputStream.Read(buffer, 0, chunkSize); 
        if (bytesRead == 0) break; 

        // write bytes to output stream 
        writeStream.Write(buffer, 0, bytesRead); 

        // report progress from time to time 
        progressBar1.Value = (int)(writeStream.Position * 100/length); 
       } while (true); 

       // report end of progress 
       LogText("Done!"); 

       writeStream.Close(); 
      } 

      // close service client 
      inputStream.Dispose(); 
      client.Close(); 

你覺得呢?

取2:

Stream stream; 
public Stream GetStream(string filename) 
{ 
    Starksoft.Net.Ftp.FtpClient ftp = new Starksoft.Net.Ftp.FtpClient(); 
    //string filePath = System.IO.Path.Combine(@"C:\UploadFiles\ServerDownloadFiles", filename); 
    //System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath); 

    ftp = new Starksoft.Net.Ftp.FtpClient("127.0.0.1"); 
    ftp.Open("testuser", "123456"); 

    stream = new MemoryStream(); 

    ftp.GetFileAsyncCompleted += new EventHandler<Starksoft.Net.Ftp.GetFileAsyncCompletedEventArgs>(ftp_GetFileAsyncCompleted); 
    this.IsBusy = true; 

    ftp.GetFileAsync(filename, stream, true); 
    return stream; 
} 

服務合同:

[ServiceContract] 
public interface IStreamingService 
{ 
    [OperationContract] 
    Stream GetStream(string filename); 

    [OperationContract] 
    Boolean GetBusyState(); 
} 

服務配置(綁定):

<basicHttpBinding> 
      <binding name="TransferService" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" transferMode="Streamed"> 
       <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/> 
       <security mode="None"> 
       </security> 
      </binding> 
     </basicHttpBinding> 
+1

我覺得它的效果很好。要麼是這樣,要麼你沒有嘗試過,或者你沒有告訴我們問題是什麼。 – 2012-01-04 01:33:07

+0

附註:我建議使用'using'語句來處理流的處理。 – 2012-01-04 02:02:20

回答

5

更新:從我最初鏈接的文章BlockingStream implementation是足以讓這個工作對我來說。

服務:

public Stream DownloadFile(string remotePath) 
{ 
    // initialize FTP client... 

    BlockingStream blockingStream = new BlockingStream(); 

    // Assign self-removing TransferComplete handler. 
    EventHandler<TransferCompleteEventArgs> transferCompleteDelegate = null; 
    transferCompleteDelegate = delegate(object sender, TransferCompleteEventArgs e) 
    { 
     // Indicate to waiting readers that 'end of stream' is reached. 
     blockingStream.SetEndOfStream(); 
     ftp.TransferComplete -= transferCompleteDelegate; 
     // Next line may or may not be necessary and/or safe. Please test thoroughly. 
     blockingStream.Close(); 
     // Also close the ftp client here, if it is a local variable. 
    }; 
    ftp.TransferComplete += transferCompleteDelegate; 

    // Returns immediately. Download is still in progress. 
    ftp.GetFileAsync(remotePath, blockingStream); 

    return blockingStream; 
} 

客戶:

StreamingService.Service1Client client = new StreamingService.Service1Client("BasicHttpBinding_IService1"); 
Stream inputStream = client.GetFile(remotePath); 
//long length = inputStream.Length; // << not available with streaming 

// write server stream to disk 
using (FileStream writeStream = new FileStream(localPath, FileMode.CreateNew, FileAccess.Write)) 
{ 
    int chunkSize = 2048; 
    byte[] buffer = new byte[chunkSize]; 
    do 
    { 
     // read bytes from input stream 
     int bytesRead = inputStream.Read(buffer, 0, chunkSize); 

     // etc. The rest like yours, but without progress reporting b/c length unknown. 

注:

  • 我直接從這篇文章複製BlockingStream代碼並粘貼成不作任何修改我的服務項目。
  • 我在BlockingStream的Read()和Write()方法中的鎖(_lockForAll)語句之後設置了斷點,並在客戶端代碼的讀取循環中加上了一個斷點。我不得不使用一個非常大的文件(至少是FTP客戶端的緩衝區大小的20倍)來查看流媒體的證據。在從FTP客戶端進行大約8次直接寫入之後,服務的另一個線程開始從流中讀取數據。經過幾輪後,服務電話返回,客戶也開始閱讀。所有三個斷點都交替出現,直到只有客戶趕上,然後才完成下載。
  • 我在測試中沒有使用真正的Starksoft FTP客戶端。我寫了一個類,用於從本地磁盤異步讀取文件,主要使用直接從the Starksoft source獲取的代碼。
  • 我也改變了服務方法簽名到Web方法與流反應最簡單的情況相匹配 - 更接近你的「取2」。如果你能使它像這樣工作,那麼你應該能夠稍後添加你的其他功能(MessageContract,文件長度等)。
  • 如果FtpClient是服務類的成員,TransferComplete事件處理程序是應該的,也是如此。
  • 請確保您有transferMode = StreamedResponse在客戶端的綁定,否則客戶端緩存中的數據即使在服務試圖流呢。
  • 請仔細檢查和測試BlockingStream,就像您在網上找到任何東西一樣!

我也是在這些在我的研究中來了,這可能是你的興趣:
List of features which can force a streaming method to buffer its response
Question including some suggestions for improving streaming speed
Complete sample application of basic streaming


是這樣實現實際上流媒體文件返回給客戶端?除非RemoteFileInfo實現IXmlSerializable,否則我認爲它不符合流方法的要求。從MSDN:在流傳輸

使用流傳輸模式

限制導致運行時執行 附加限制。

跨越流傳輸時發生的操作可以有合同 與至多一個輸入或輸出參數。該參數對應於消息的整個主體 ,並且必須是消息,衍生的 類型的Stream或IXmlSerializable實現。返回 操作值相當於具有輸出參數。

我想您的實現實際上是緩衝數據三次:第一次到結果的FileByteStream屬性服務器上的文件,再次,在客戶端的第三次,之前的服務電話,甚至回報。您可以刪除enabling streaming on the binding中的兩個緩衝延遲,並直接從服務方法返回可讀的FileStream對象。其他屬性可以在返回消息中設置爲標題。一個例子見this answer

也許你可以做一個更好的,雖然。根據Starksoft doc,在調用GetFileAsync時,「輸出流必須是可寫的,並且可以是任何流對象」。它可能可以爲您打造的Stream的實現,使您可以使用單個流對象用於所有目的。您將創建流對象,將其直接傳遞給GetFileAsync方法,然後直接將其返回給客戶端,而無需等待整個文件的下載。這可能是矯枉過正,但here是一個阻塞讀寫流的實現,你可以嘗試。

+0

非常interresting,非常感謝提示,我會嘗試並取回 – amamdouh 2012-01-04 09:14:14

+0

我改變了代碼,以便在GetFileAsync寫入流時返回流引用。 但是,只要客戶端試圖訪問它,它就會給出「無法訪問已關閉的文件」異常。但是流是讀/寫的。 也試圖使用內存流而不是文件流,但出於同樣的原因「不能訪問封閉的流」失敗 – amamdouh 2012-01-05 00:08:29

+0

@ user1128929我不能告訴是什麼原因導致錯誤,沒有更多的上下文。無論如何,沒有內置的Stream類會爲你想做的事情工作。您需要查看FTP客戶端的源代碼,甚至可以查看部分WCF流式代碼,然後編寫或修改支持兩者要求的Stream實現。 – Kimberly 2012-01-05 01:56:51

相關問題