2010-04-28 5 views
12

Ist es richtig, dass in WCF kann ich keinen Dienst schreiben zu einem Stream, der vom Client empfangen wird?WCF und Streaming-Anforderungen und Antworten

Streaming wird in WCF für Anfragen, Antworten oder beides unterstützt. Ich möchte ein Szenario unterstützen, in dem der Datengenerator (entweder der Client im Falle einer gestreamten Anfrage oder der Server im Falle einer gestreamten Antwort) in den Stream schreiben kann. Wird das unterstützt?

Die Analogie ist der Response.OutputStream von einer ASP.NET-Anfrage. In ASPNET kann jede Seite Write im Ausgabestream aufrufen, und der Inhalt wird vom Client empfangen. Kann ich in einem WCF-Dienst etwas Ähnliches tun? - Write auf einen Stream, der vom Client empfangen wird?

Lassen Sie mich mit einer WCF Illustration erklären. Das einfachste Beispiel für Streaming in WCF ist der Dienst, der einen FileStream an einen Client zurückgibt. Dies ist eine gestreamte Antwort. Der Server-Code, dies zu implementieren, ist wie folgt:

[ServiceContract] 
public interface IStreamService 
{ 
    [OperationContract] 
    Stream GetData(string fileName); 
} 
public class StreamService : IStreamService 
{ 
    public Stream GetData(string filename) 
    { 
     return new FileStream(filename, FileMode.Open) 
    } 
} 

Und der Client-Code ist wie folgt:

StreamDemo.StreamServiceClient client = 
    new WcfStreamDemoClient.StreamDemo.StreamServiceClient(); 
Stream str = client.GetData(@"c:\path\on\server\myfile.dat"); 
do { 
    b = str.ReadByte(); //read next byte from stream 
    ... 
} while (b != -1); 

(Beispiel aus http://blog.joachim.at/?p=33)

Klar, nicht wahr? Der Server gibt den Stream an den Client zurück, und der Client ruft Read darauf auf.

Ist es möglich, dass der Client einen Stream bereitstellt und der Server Write darauf aufruft?
Anders als ein Pull-Modell - wo der Client Daten vom Server bezieht - ist es ein Push-Modell, bei dem der Client den "Sink" -Stream bereitstellt und der Server in ihn schreibt. Der serverseitige Code sieht etwa so aus:

[ServiceContract] 
public interface IStreamWriterService 
{ 
    [OperationContract] 
    void SendData(Stream clientProvidedWritableStream); 
} 
public class DataService : IStreamWriterService 
{ 
    public void GetData(Stream s) 
    { 
     do { 
      byte[] chunk = GetNextChunk(); 
      s.Write(chunk,0, chunk.Length); 
     } while (chunk.Length > 0); 
    } 
} 

Ist dies in WCF möglich, und wenn ja, wie? Welche Konfigurationseinstellungen sind für die Bindung, die Schnittstelle usw. erforderlich? Wie ist die Terminologie?

Vielleicht wird es einfach funktionieren? (Ich habe es nicht versucht)

Danke.

Antwort

8

Ich bin ziemlich sicher, dass es keine Kombination von WCF-Bindungen, die Sie buchstäblich Schreib in den Stream des Kunden ermöglicht.Es scheint logisch, dass es da sein sollte, vorausgesetzt, irgendwo unter der Oberfläche wird es definitiv eine NetworkStream geben, aber sobald man beginnt, Verschlüsselung und all diese spaßigen Sachen hinzuzufügen, müsste WCF wissen, wie man diesen Stream umwandelt, um ihn zu verwandeln "echte" Nachricht, was ich nicht glaube.

Das I-need-to-return-a-stream-aber-Komponente-X-will-zu-schreiben-zu-eins-Szenario ist eine häufige, und ich habe eine Catch-All-Lösung, die ich Verwenden Sie dieses Szenario, um einen bidirektionalen Stream zu erstellen und einen Worker-Thread zum Schreiben zu generieren. Die einfachste und sicherste Weg, dies zu tun (mit dem ich meine, die Art und Weise, die das Schreiben am wenigsten Code und damit am wenigsten wahrscheinlich Buggy bedeutet) ist anonyme Rohrleitungen zu verwenden.

Hier ist ein Beispiel für ein Verfahren, das eine AnonymousPipeClientStream zurückgibt, die in WCF-Nachrichten verwendet werden können, Ergebnisse MVC, und so weiter - wo immer Sie wollen die Richtung invertieren:

static Stream GetPipedStream(Action<Stream> writeAction) 
{ 
    AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(); 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     using (pipeServer) 
     { 
      writeAction(pipeServer); 
      pipeServer.WaitForPipeDrain(); 
     } 
    }); 
    return new AnonymousPipeClientStream(PipeDirection.In, pipeServer.ClientSafePipeHandle); 
} 

Ein Beispiel für die Verwendung dieser würde sein:

static Stream GetTestStream() 
{ 
    string data = "The quick brown fox jumps over the lazy dog."; 
    return GetPipedStream(s => 
    { 
     StreamWriter writer = new StreamWriter(s); 
     writer.AutoFlush = true; 
     for (int i = 0; i < 50; i++) 
     { 
      Thread.Sleep(50); 
      writer.WriteLine(data); 
     } 
    }); 
} 

Nur zwei Einsprüche über diesen Ansatz:

  1. Es wird fehlschlagen, wenn die writeAction alles tut, um den Stream zu disponieren (deshalb entsorgt die Testmethode die StreamWriter nicht wirklich - weil das den zugrundeliegenden Strom wegwerfen würde);

  2. Es kann fehlschlagen, wenn die writeAction keine Daten tatsächlich schreibt, weil WaitForPipeDrain sofort zurückkehrt und eine Race-Bedingung erstellen. (Wenn dies ein Problem ist, können Sie ein wenig mehr Code defensiv, dies zu vermeiden, aber es erschwert Dinge viel für einen seltenen Rand Fall.)

Hoffentlich, die in Ihrem speziellen Fall hilft.

+0

PS Ich hätte in der Antwort hinzufügen sollen - eine Bestandsaufnahme darüber machen, wie viele Daten wirklich gestreamt werden müssen. Dies ist nützlich für mehrere MB oder mehr, aber wenn Sie nur ein paar KB zu senden haben, würde es weniger kosten, einfach einen 'MemoryStream' zu erstellen, auf den Ihre Komponente schreiben und dann zurückgeben kann. – Aaronaught

+0

Whoa, ja !. Lass mich das jetzt verstehen. Bitte bestätigen; (1) AnonymousPipe {Client, Server} Stream ist ein Paar Klassen, die nur serverseitig verwendet werden. (2) Was an den Client zurückgegeben wird, ist ein System.IO.Stream. – Cheeso

+0

ps: Ich habe diese Frage schon einmal gestellt, siehe http://stackoverflow.com/questions/1499520. Ich bin sehr erfreut, eine allgemeine, einfache Antwort zu bekommen. – Cheeso

2

WCF-Streaming funktioniert in beide Richtungen - Ihre Clients können Inhalte in einen Stream hochladen, aber Ihr WCF-Dienst kann auch Daten in einem Stream senden. Sie können definitiv einen Dienst schreiben, der große Dateien basierend auf ihrem Dateinamen oder einer ID oder etwas zurücksendet - absolut.

Wenn Sie Daten vom Service zurücksenden möchten, müssen Sie dies im Rückgabewert Ihrer Servicemethode tun, die vom Typ Stream sein muss - Sie können (so weit ich weiß) nicht "liefern" Stream zum Schreiben - der WCF-Dienst erstellt einen Antwortdatenstrom und sendet ihn zurück.

Haben Sie MSDN Streaming Message Transfer oder David Wood's blog post oder Kjell-Sverre's blog post auf WCF-Streaming ausgecheckt? Sie alle zeigen schön, welche Konfigurationseinstellungen Sie benötigen (grundsätzlich setzen Sie TransferMode in Ihrer Bindungskonfiguration auf Streamed, StreamedRequest oder).

+0

Also, Marc, was Sie sagen, ist ... für eine gestreamte Antwort muss ein Dienst einen Stream an den Client zurückgeben. Mit anderen Worten, es gibt kein direktes Analog zu dem ASPNET Response.OutputStream, in das ein Dienst direkt schreiben könnte. Richtig? – Cheeso

+0

@Cheeso: Korrekt - der Dienst muss einen Stream zurückgeben, aber kann nicht einfach auf einen Stream im Client schreiben - der Server und der Client haben keine ständige Verbindung in WCF - Sie können nur serialisierte Nachrichten austauschen (als ganz oder chunked wie beim Streaming) –