使用WCF从远程FTP服务器下载和流式传输文件

时间:2012-01-04 00:30:30

标签: c# wcf iis ftp streaming

我正在构建一个解决方案,其中WCF服务充当必须通过FTP协议(Linux服务器)和Windows客户端应用程序远程访问的FTP服务器之间的网关。服务本身将托管在Windows IIS服务器上。

我的模型基于一篇关于使用WCF通过http传输文件的文章,但问题是:

我必须先等待文件在Windows服务器上下载,然后再将其放到客户端,这可能是一个主要的性能问题。我想直接将文件从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 个答案:

答案 0 :(得分: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()方法中的lock(_lockForAll)语句之后设置了断点,并在客户端代码的read循环中添加了断点。我不得不使用一个非常大的文件(至少是FTP客户端缓冲区大小的20倍)来查看流媒体的证据。从FTP客户端大约8次直接写入后,该服务的其他线程开始从流中读取。几轮之后,服务调用返回,客户端也开始阅读。所有三个断点都被交替击中,直到只有客户端赶上,然后最终完成下载。
  • 我在测试中没有使用真正的Starksoft FTP客户端。我编写了一个类,它从异步读取本地磁盘中的文件,主要使用直接从the Starksoft source获取的代码。
  • 我还更改了服务方法签名,以匹配Web方法的最简单情况和流式响应 - 更接近您的'take 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

但也许你可以做得更好。根据{{​​3}},在对GetFileAsync的调用中,“输出流必须是可写的,并且可以是任何流对象”。 可能可以创建Stream的实现,允许您为所有目的使用单个流对象。您将创建流对象,将其直接传递给GetFileAsync方法,然后将其直接返回给客户端,而无需等待下载整个文件。这可能有些过分,但Starksoft doc是您可以尝试的阻塞读写流的实现。