загрузка и потоковая передача файлов с помощью WCF с удаленного FTP-сервера - PullRequest
2 голосов
/ 04 января 2012

Я создаю решение, в котором служба WCF выступает в качестве шлюза между FTP-сервером, к которому он должен получить удаленный доступ по протоколу FTP (сервер Linux), и клиентским приложением Windows. Сам сервис будет размещен на сервере Windows IIS.

Я основал свою модель на статье о потоковой передаче файлов через http с использованием WCF, но проблема заключается в следующем:

Мне нужно сначала дождаться загрузки файла на сервер 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();
}

Service Config (Binding):

<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 Ответ

5 голосов
/ 04 января 2012

Обновление: Реализации BlockingStream из статьи, на которую я изначально ссылался, было достаточно, чтобы заставить меня работать.

Услуги:

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 непосредственно из этой статьи и вставил его в свой сервисный проект без изменений.
  • Я устанавливаю точки останова после операторов блокировки (_lockForAll) в методах Read () и Write () объекта BlockingStream, а также точку останова в цикле чтения клиентского кода. Мне пришлось использовать довольно большой файл (по крайней мере, в 20 раз превышающий размер буфера FTP-клиента), чтобы увидеть доказательства потоковой передачи. Приблизительно после 8 прямых записей с FTP-клиента другой поток службы начинает чтение из потока. После нескольких раундов сервисный звонок вернулся, и клиент тоже начал читать. Все три точки прерывания были поочередно достигнуты, пока только клиент не догнал, а затем, наконец, не завершил загрузку.
  • В моем тестировании я не использовал настоящий FTP-клиент Starksoft. Я написал класс, который читает файл с локального диска асинхронно, используя в основном код, взятый непосредственно из источника Starksoft .
  • Я также изменил сигнатуру метода сервиса, чтобы он соответствовал простейшему случаю веб-метода с потоковым ответом - ближе к вашему «дублю 2». Если вы можете заставить его работать так, вы сможете добавить другие функции (MessageContract, длина файла и т. Д.) Позже.
  • Если ваш FtpClient является членом вашего класса обслуживания, то и обработчик события TransferComplete также должен быть.
  • Убедитесь, что у вас есть TransferMode = StreamedResponse в привязке вашего клиента, в противном случае клиент будет буферизовать данные, даже когда служба пытается их передать.
  • Пожалуйста, просмотрите и протестируйте BlockingStream так же тщательно, как и все, что можно найти в Интернете!

Я также сталкивался с этим в своем исследовании, которое может вас заинтересовать:
Список функций, которые могут заставить потоковый метод буферизовать свой ответ
Вопрос, включая некоторые предложения по улучшению скорости потоковой передачи
Полный пример применения базовой потоковой передачи


Эта реализация действительно передает файл обратно клиенту? Если RemoteFileInfo не реализует IXmlSerializable, я не думаю, что он соответствует требованиям для метода потоковой передачи. От MSDN :

Ограничения на потоковые передачи

Использование режима потоковой передачи приводит к принудительному выполнению времени выполнения дополнительные ограничения.

Операции, выполняемые через потоковый транспорт, могут иметь контракт с не более чем одним входным или выходным параметром. Этот параметр соответствует ко всему телу сообщения и должно быть сообщением, производным тип Stream или реализация IXmlSerializable. Имея возвращение значение для операции эквивалентно наличию выходного параметра.

Я думаю, что ваша реализация на самом деле буферизует данные три раза: один раз в файл на сервере, снова в свойство FileByteStream результата и третий раз на клиенте, прежде чем вызов службы даже вернется. Вы можете удалить две из этих задержек буферизации с помощью , включив потоковую передачу для привязки и возвращая читаемый объект FileStream непосредственно из метода службы. Другие свойства могут быть установлены в качестве заголовков в ответном сообщении. См. этот ответ для примера.

Может быть, вы можете сделать один лучше, хотя.Согласно Starksoft doc , при вызове GetFileAsync «Выходной поток должен быть доступен для записи и может быть любым объектом потока». может создать для вас реализацию Stream, которая позволит вам использовать один объект потока для всех целей.Вы должны создать объект потока, передать его непосредственно методу GetFileAsync, а затем вернуть его непосредственно клиенту, не дожидаясь загрузки всего файла.Это может быть излишним, но здесь является реализацией блокирующего потока чтения-записи, который вы можете попробовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...