2 Stimmen

Das Herunterladen und Streamen von Dateien mit WCF von einem entfernten FTP-Server

Ich entwickle eine Lösung, bei der ein WCF-Dienst als Gateway zwischen einem FTP-Server agiert, auf den er remote über das FTP-Protokoll (Linux-Server) zugreifen muss, und einer Windows-Clientanwendung. Der Dienst selbst wird auf einem Windows IIS-Server gehostet.

Ich habe mein Modell auf einem Artikel über das Streamen von Dateien über http mit WCF basiert, aber das Problem ist:

Ich muss auf die Datei warten, die zunächst auf dem Windows Server heruntergeladen werden soll, bevor ich sie an den Client streamen kann, und das könnte ein größeres Leistungsproblem sein. Ich möchte die Dateien direkt vom FTP-Server zum Client streamen, ohne sie zuerst herunterzuladen.

Hier ist der Code..

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");

        // hier wird auf die Datei gewartet, die vom FTP-Server heruntergeladen werden soll
        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();

        // dies wird gelesen und an den Client gestreamt
        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;

 }

Der Client sieht so aus:

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

            LogText("Start");

            // Zieldatei löschen, falls bereits vorhanden
            string filePath = System.IO.Path.Combine("Download", textBox1.Text);
            if (System.IO.File.Exists(filePath)) System.IO.File.Delete(filePath);

            // Stream vom Server abrufen
            System.IO.Stream inputStream;
            string fileName = textBox1.Text;
            long length = client.DownloadFile(ref fileName, out inputStream);

            // Serverstream auf Festplatte schreiben
            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
                {
                    // Bytes aus dem Eingabestream lesen
                    int bytesRead = inputStream.Read(buffer, 0, chunkSize);
                    if (bytesRead == 0) break;

                    // Bytes in den Ausgabestream schreiben
                    writeStream.Write(buffer, 0, bytesRead);

                    // Fortschritt von Zeit zu Zeit melden
                    progressBar1.Value = (int)(writeStream.Position * 100 / length);
                } while (true);

                // Fortschritt melden
                LogText("Fertig!");

                writeStream.Close();
            }

            // Serviceclient schließen
            inputStream.Dispose();
            client.Close();

Was denkst du?

Take 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(ftp_GetFileAsyncCompleted);
    this.IsBusy = true;

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

Servicevertrag:

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

    [OperationContract]
    Boolean GetBusyState();
}

Servicekonfiguration (Binding):

5voto

Kimberly Punkte 2617

Aktualisierung: Die BlockingStream-Implementierung aus dem Artikel, den ich ursprünglich verlinkt habe, hat ausgereicht, um dies für mich zum Laufen zu bringen.

Dienst:

public Stream DownloadFile(string remotePath)
{
    // FTP-Client initialisieren...

    BlockingStream blockingStream = new BlockingStream();

    // Selbstentfernung des Übertragungskomplett-Handlers zuweisen.
    EventHandler transferCompleteDelegate = null;
    transferCompleteDelegate = delegate(object sender, TransferCompleteEventArgs e)
    {
        // Warten, bis 'Ende des Streams' erreicht ist.
        blockingStream.SetEndOfStream();
        ftp.TransferComplete -= transferCompleteDelegate;
        // Nächste Zeile kann notwendig und/oder sicher sein oder auch nicht. Bitte gründlich testen.
        blockingStream.Close();
        // FTP-Client hier auch schließen, falls dies eine lokale Variable ist.
    };
    ftp.TransferComplete += transferCompleteDelegate;

    // Gibt sofort zurück. Download läuft noch.
    ftp.GetFileAsync(remotePath, blockingStream);

    return blockingStream;
}

Client:

StreamingService.Service1Client client = new StreamingService.Service1Client("BasicHttpBinding_IService1");
Stream inputStream = client.GetFile(remotePath);
//long length = inputStream.Length; // << nicht verfügbar bei Streaming

// Serverstream auf Festplatte schreiben
using (FileStream writeStream = new FileStream(localPath, FileMode.CreateNew, FileAccess.Write))
{
    int chunkSize = 2048;
    byte[] buffer = new byte[chunkSize];
    do
    {
        // Bytes aus Eingabestream lesen
        int bytesRead = inputStream.Read(buffer, 0, chunkSize);

        // usw. Der Rest wie bei Ihnen, aber ohne Fortschrittsberichterstattung, da Länge unbekannt.

Notizen:

  • Ich habe den BlockingStream-Code direkt aus diesem Artikel kopiert und ohne Modifikationen in mein Serviceprojekt eingefügt.
  • Ich habe Breakpoints nach den lock(_lockForAll)-Anweisungen in den Read()- und Write()-Methoden von BlockingStream sowie einen Breakpoint in der Lese-Schleife des Clientcodes gesetzt. Um den Streaming-Nachweis zu sehen, musste ich eine recht große Datei verwenden (mindestens 20 Mal die Puffergröße des FTP-Clients). Nach etwa 8 Schreibvorgängen des FTP-Clients begann der andere Thread des Dienstes, aus dem Stream zu lesen. Nach einigen Runden davon gab der Dienstanruf zurück, und der Client begann auch zu lesen. Alle drei Breakpoints wurden abwechselnd getroffen, bis nur der Client aufgeholt hatte und dann schließlich den Download abgeschlossen hatte.
  • Ich habe den echten Starksoft-FTP-Client nicht in meinen Tests verwendet. Ich habe eine Klasse geschrieben, die eine Datei von der lokalen Festplatte asynchron liest, hauptsächlich mit Code, der direkt aus der Starksoft-Quelle übernommen wurde.
  • Ich habe auch die Service-Methoden-Signatur geändert, um zum einfachsten Fall einer Webmethode mit gestreamter Antwort zu passen - näher an Ihrem 'Versuch 2'. Wenn Sie es so zum Laufen bringen können, sollten Sie später Ihre anderen Funktionen (MessageContract, Dateilänge usw.) hinzufügen können.
  • Wenn Ihr FtpClient ein Element Ihrer Serviceklasse ist, sollte der TransferComplete-Ereignishandler auch dazu gehören.
  • Vergewissern Sie sich, dass Sie transferMode=StreamedResponse in der Bindung Ihres Clients haben, sonst wird der Client die Daten zwischenspeichern, auch wenn der Dienst versucht, sie zu streamen.
  • Überprüfen und testen Sie den BlockingStream bitte so sorgfältig wie alles andere, was Sie im Internet finden!

Auf meiner Recherche bin ich auch auf diese Informationen gestoßen, die für Sie interessant sein könnten:
Liste der Funktionen, die dazu führen können, dass eine Streaming-Methode ihre Antwort zwischenspeichert
Frage mit einigen Vorschlägen zur Verbesserung der Streaming-Geschwindigkeit
Vollständige Beispielanwendung des grundlegenden Streamings


Streamt diese Implementierung tatsächlich die Datei zurück an den Client? Es sei denn, RemoteFileInfo implementiert IXmlSerializable, glaube ich nicht, dass es den Anforderungen für eine Streaming-Methode entspricht. Von MSDN:

Einschränkungen bei gestreamten Übertragungen

Die Verwendung des gestreamten Übertragungsmodus führt dazu, dass die Laufzeit zusätzliche Einschränkungen durchsetzt.

Operationen, die über einen gestreamten Transport stattfinden, können einen Vertrag mit höchstens einem Ein- oder Ausgabeparameter haben. Dieser Parameter entspricht dem gesamten Nachrichteninhalt und muss ein Message, eine abgeleitete Art von Stream oder eine Implementierung von IXmlSerializable sein. Das Vorhandensein eines Rückgabewerts für eine Operation entspricht dem Vorhandensein eines Ausgabeparameters.

Ich denke, Ihre Implementierung puffert die Daten tatsächlich dreimal: einmal in eine Datei auf dem Server, erneut in die FileByteStream-Eigenschaft des Ergebnisses und das dritte Mal im Client, bevor der Dienstanruf überhaupt zurückkehrt. Sie könnten zwei dieser Zwischenspeicherungsverzögerungen entfernen, indem Sie das Streamen an der Bindung aktivieren und das lesbare FileStream-Objekt direkt aus Ihrer Service-Methode zurückgeben. Die anderen Eigenschaften könnten als Header in der Rückmeldungsnachricht festgelegt werden. Siehe diese Antwort für ein Beispiel.

Vielleicht können Sie aber auch noch einen draufsetzen. Laut der Starksoft-Dokumentation kann in dem Aufruf von GetFileAsync "Der Ausgabestream beschreibbar sein und kann ein beliebiges Streamobjekt sein". Es könnte möglich sein, eine Implementierung von Stream zu erstellen, die es Ihnen ermöglicht, ein einziges Streamobjekt für alle Zwecke zu verwenden. Sie würden das Streamobjekt erstellen, es direkt an die Methode GetFileAsync übergeben und es dann direkt an den Client zurückgeben, ohne auf den vollständigen Download der Datei zu warten. Dies könnte überflüssig sein, aber hier ist eine Implementierung eines blockierenden Lese-Schreib-Streams, die Sie ausprobieren könnten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X