Streaming with WCF - Part 1: Custom Stream Implementation
Somewhat recently, I've been adding some tests that use streaming with WCF. I'm not going to get into the specifics of my tests, but I will say that, for me, setting up WCF for streaming and creating oddball custom stream implementations wasn't exactly intuitive to me.
After realizing I wasn't the only one stumbling around with this, I decided I should write a series of posts related to streaming in WCF. So with that, let me start the posts.
Streaming with WCF
Part 1: Custom Stream Implementations
Before I get into details of WCF and streaming, I first want to give an example of a custom stream implementation. See, anyone can stream to or from, say, a file with the following code:
public static void TestFileStream()
{
// Writing
FileStream fs = new FileStream("C:\\test.txt", FileMode.OpenOrCreate);
byte[] bytesToWrite = Encoding.ASCII.GetBytes("Yeah, this is the API for writing a string to a file.");
fs.Write(bytesToWrite, 0, bytesToWrite.Length);
fs.Close();
// Reading
fs = new FileStream("C:\\test.txt", FileMode.Open);
byte[] buffer = new byte[bytesToWrite.Length];
fs.Read(buffer, 0, bytesToWrite.Length);
Console.WriteLine(Encoding.ASCII.GetString(buffer));
}
"fs" is the stream, obviously. Which is fine for basic tests, but what if I wanted to send an infinite stream? What if I wanted to open a stream to the server and start pushing bytes, one at a time, until I hit CTRL + C? What if someone had a WCF service that would stream data back to the client on the fly? How do you write a custom stream for that?
- I start by extending the Stream class:
public class ContinuousStream : Stream
{
- Next, I think about what I want to do. I want it to be able to be used by a WCF service to write bytes to the wire continually. How do you override the way a stream writes bytes? Why, override the Read method, of course:
public override int Read(byte[] buffer, int offset, int count)
{
// Allow Read to continue forever
// Just fill buffer with as many random bytes as necessary.
int seed = DateTime.Now.Millisecond;
Random rand = new Random(seed);
byte[] randomBuffer = new byte[count];
rand.NextBytes(randomBuffer);
randomBuffer.CopyTo(buffer, offset);
totalBytesRead += count;
return count;
}
Now, it's time to give some background information about how WCF sends a stream on the wire. If I were to use this in a service operation called, "DownloadStream" it would return an object of type Stream and take no parameters. I would just have to return a new instance of this object. When the service gets invoked by a client call, assuming it's using streamed transfer mode, WCF will basically hand this object down to the transport which will call the Read method, passing in a buffer that will be sent on the wire. Assuming Read doesn't return 0, indicating there are no bytes to read, it will continually call Read and send the filled buffers on the wire.
With that bit of knowledge, the first thing I want to add to the Continuous stream is a throttle to slow down the time between Read calls. To do this, I add a property to the ContinuousStream class:
public TimeSpan ReadThrottle { get; set; }
And I change the implementation of Read:
public override int Read(byte[] buffer, int offset, int count)
{
// Allow Read to continue forever
// Just fill buffer with as many random bytes as necessary.
int seed = DateTime.Now.Millisecond;
Random rand = new Random(seed);
byte[] randomBuffer = new byte[count];
randomBuffer.CopyTo(buffer, offset);
totalBytesRead += count;
if (ReadThrottle != TimeSpan.Zero)
{
Thread.CurrentThread.Join(ReadThrottle);
}
return count;
}
How you override the Read method is totally up to you. You could create a WCF client in here and call a service to get some data which you then put in the buffer somehow.
Next time, I'll show some more minor enhancements to the ContinuousStream and use it in a WCF service.
Comments
Anonymous
August 18, 2011
please let me know,where you are sending one byte at a time to the clinetAnonymous
January 03, 2013
Hello James, I came across your blog when trying to achieve the same thing. This is very helpful. I have one question though. You mentioned that "WCF will basically hand this object down to the transport which will call the Read method, passing in a buffer that will be sent on the wire". During debugging I found that the byte array that was passed to the Read method has a default length of 256. Is there any way to increase it? Thanks, RayAnonymous
January 03, 2013
The comment has been removed