This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.
Last week I introduced the channel model in WCF, with an emphasis on the channels at the client side. But that’s usually just half of the story - although the example I provided didn’t require a server-side channel counterpart, in many cases there are two channels, on the client and the server, acting together to provide some functionality to the WCF pipeline. This post will go into a little more detail on the server aspects of channels, especially how they’re created and inserted into the WCF stack.
Unlike on the client, where a channel factory is used for creating the channel instances, on the server side WCF uses channel listeners for that purpose. A factory basically sits there waiting for a call to be made (IChannelFactory<T>.CreateChannel) to return a channel, and a client channel sits idle until some channel (or the service model proxy) makes a call to send a message along (e.g., IRequestChannel.Request or IOutputChannel.Send). A channel listener, on the other hand, will actively try to “pump” channels from the listener sitting below it in the stack (the transport channel will sit listening for sockets or something similar from the network), in an “accept loop” similar to the one shown below.
Basically, the listener will be asked to “accept” a channel for a certain time, and it should return it whenever one is available. If no channels are available after the timeout, the listener will throw. In many cases, the channel will be available right away – for example, the listener created by the HttpTransportBindingElement returns a “channel acceptor” class which waits for incoming connections in the HTTP port it’s listening to. The “server” channels will then follow a similar pumping pattern, this time waiting for incoming messages to arrive. Notice that this is similar to the way the HttpListener class is often used for for creating a generic HTTP server (although HttpListener has an event-based option which makes this a little easier; no such luck for WCF channels, unfortunately).
Notice that those snippets show the synchronous versions of the pump messages, but the WCF runtime by default uses their asynchronous versions (and for channels, the Try(Timeout, out result) pattern): IChannelListener<TChannel>.BeginAcceptChannel / IChannelListener<TChannel>.EndAcceptChannel for the listener, IReplyChannel.BeginTryReceiveRequest / IReplyChannel.EndTryReceiveRequest for reply channels (or IInputChannel.BeginTryReceive / IInputChannel.EndTryReceive for input channels). The calls to the channels can be changed to use the synchronous versions (TryReceiveRequest / TryReceive) by using the SynchronousReceiveBehavior endpoint behavior.
There are essentially two kinds of server channels as far as receiving messages: input channels and reply channels – duplex channels are just a pair of input / output channel, and session channels are the same as the non-session ones with an added identifier for the session for the communication. On input channels (used in datagram-like communication), the client simply sends a message and “forgets”, not waiting for a response. The input channel implements this behavior by simply returning the message directly to the caller on the Receive (or TryReceive, or Begin/EndReceive, or Begin/EndTryReceive). After the channel returns the message, it will go back to the pump loop waiting for a new one, and that message is “done”.
Reply channels, on the other hand, need to send a response to the message which it received, so instead of simply delivering the message to the caller, it will return a RequestContext object. The request context is the object which can be used to send the reply which is correlated to the incoming request. The request context has a property which holds a reference to the incoming request, and some methods to send a reply to the caller – it’s the responsibility of the code which called IReplyChannel.ReceiveRequest (or its variants) to call RequestContext.Reply (or BeginReply / EndReply) on the context to return a response to the caller. If this is not done in a timely manner, the channel will abort the request. The
Just like the client, it all starts at the binding. So one needs to write a binding element, and create a binding containing this element. The binding element class would override CanBuildChannelListener<TChannel> (to determine whether the channel shape is supported) and BuildChannelListener<TChannel> to actually return a channel listener which is capable of accepting the requested channel type. The channel listener implementation needs to override the AcceptChannel methods (or OnAcceptChannel and its asynchronous / try variants), if the class inherits from ChannelListenerBase<TChannel>), besides a lot of boilerplate methods (Open, BeginOpen, EndOpen, Close, etc.) to finally return the channel which will be used in the pipeline. To illustrate the verboseness of all of this, this is I think the simplest code to write a server channel that I can think of (it just passes the message along, and supports only IReplyChannel, although supporting IInputChannel wouldn’t add a lot more code).
Again, that’s a lot of code. But that’s a good starting point if you want to do something simple with the message.
This has been a busy time at work, so I’ll skip the scenario code for this week’s post. There are some very interesting samples using channels (both server and client ones) at the WCF Channel Extensibility samples page. Also, there are some nice posts from Nicholas Allen about channels in WCF which are a great source of information for low-level WCF details.
[Back to the index]