Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
437 views
in Technique[技术] by (71.8m points)

.net - WCF and streaming requests and responses

Is it correct that in WCF, I cannot have a service write to a stream that is received by the client?

Streaming is supported in WCF for requests, responses, or both. I would like to support a scenario where the data generator (either the client in case of streamed request, or the server in case of streamed response) can Write on the stream. Is this supported?

The analogy is the Response.OutputStream from an ASP.NET request. In ASPNET, any page can invoke Write on the output stream, and the content is received by the client. Can I do something similar in a WCF service - invoke Write on a stream that is received by the client?

Let me explain with a WCF illustration. The simplest example of Streaming in WCF is the service returning a FileStream to a client. This is a streamed response. The server code to implement this, is like this:

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

And the client code is like this:

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

(example taken from http://blog.joachim.at/?p=33)

Clear, right? The server returns the Stream to the client, and the client invokes Read on it.

Is it possible for the client to provide a Stream, and the server to invoke Write on it?
In other words, rather than a pull model - where the client pulls data from the server - it is a push model, where the client provides the "sink" stream and the server writes into it. The server-side code might look similar to this:

[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); 
    }
}

Is this possible in WCF, and if so, how? What are the config settings required for the binding, interface, etc? What is the terminology?

Maybe it will just work? (I haven't tried it)

Thanks.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I'm fairly certain that there's no combination of WCF bindings that will allow you to literally write to the client's stream. It does seem logical that there should be, given that somewhere beneath the surface there's definitely going to be a NetworkStream, but once you start adding encryption and all that fun stuff, WCF would have to know how to wrap this stream to turn it into the "real" message, which I don't think it does.

However, the I-need-to-return-a-stream-but-component-X-wants-to-write-to-one scenario is a common one, and I have a catch-all solution that I use for this scenario, which is to create a two-way stream and spawn a worker thread to write to it. The easiest and safest way to do this (by which I mean, the way that involves writing the least code and therefore least likely to be buggy) is to use anonymous pipes.

Here's an example of a method that returns an AnonymousPipeClientStream that can be used in WCF messages, MVC results, and so on - anywhere you want to invert the direction:

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

An example usage of this would be:

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

Just two caveats about this approach:

  1. It will fail if the writeAction does anything to dispose the stream (that's why the test method doesn't actually dispose the StreamWriter - because that would dispose the underlying stream);

  2. It might fail if the writeAction doesn't actually write any data, because WaitForPipeDrain will return immediately and create a race condition. (If this is a concern, you can code a little more defensively to avoid this, but it complicates things a lot for a rare edge case.)

Hopefully that helps in your specific case.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...