Condividi tramite


Meet the WCF Channel Model – Part 2

Most WCF developers won’t interact directly with channels or have to think about them much. The Service Model, WCF’s programming model, provides abstractions that layer on top of channels to give a “method and typed parameters” programming model. However, many developers will want to write custom channels for the reasons I mentioned in my previous post. For those developers, it’s interesting to understand how the channels are used (indirectly) by a developer building a WCF service and/or client.

WCF services and clients are based on the concept of endpoints. An endpoint consists of an address, a binding and a contract. The address is the endpoint’s network location (the where). The binding specifies how the endpoint communicates with the world (the how) and the contract is the collection of operations the endpoint exposes (the what). For the purpose of this discussion, the binding is the most interesting of the three.

A service developer defines an endpoint by specifying its three components. For example:

public static void Main()
{
ServiceHost host = new ServiceHost(typeof(StreamServer));

 host.AddEndpoint(
typeof(IStreamService),
new ChunkingTcpBinding(),
new Uri("net.tcp://localhost/StreamService"));

 host.Open();

 Console.WriteLine("stream server started....");
Console.ReadLine();
}

The call to host.AddEndpoint defines the endpoint. The call to host.Open creates the channel stack that will listen for and process messages targeted at the endpoint. Each binding is really a collection of binding elements each contributing to building the channel stack. For example, this is the ChunkingTcpBinding:

public class ChunkingTcpBinding : Binding
{
public ChunkingTcpBinding()
{
...
}
public override BindingElementCollection CreateBindingElements()
{
BindingElementCollection col =
new BindingElementCollection();
ChunkingBindingElement cbe = new ChunkingBindingElement();
TcpTransportBindingElement tcp =
new TcpTransportBindingElement();
tcp.TransferMode = TransferMode.Buffered;
tcp.MaxMessageSize = Int32.MaxValue;
col.Add(cbe);
col.Add(tcp);
return col.Clone();
}

}

Each binding element inherits from BindingElement which has a couple of interesting methods:

public abstract class BindingElement
{

public virtual IChannelListener<TChannel>
BuildChannelListener<TChannel>(ChannelBuildContext context)
where TChannel : class, IChannel
{
}

public virtual IChannelFactory<TChannel>
BuildChannelFactory<TChannel>(ChannelBuildContext context)
{
}

... other methods not shown ...

}

The Service Model calls BuildChannelListener to get back a ChannelListener that is added to the channel stack to listen for incoming channels. Think of an incoming channel as an incoming connection request. Once established, it can be used for receiving messages. Here it is visually:

On the send side (e.g. on the client side), the Service Model calls BuildChannelFactory to get back a ChannelFactory. A ChannelFactory is used to build a channel that can be used for sending messages. Here is this part visually:

 

 

You may be wondering why there are two factory abstractions: binding element and channelfactory/channellistener. Turns out there are different two categories of things one may want to do in a factory class that creates channels and those two categories are best separated in two factories. The categories are: programming model-specific work and channel-specific work. Programming model-specific work that belongs in binding elements includes things like varying behavior based on settings on the endpoint’s contract or implementation and reading from config. Channel-specific work that belongs in channel factories/listeners includes things like pooling resources across channels (e.g. network connections).

The next interesting thing to talk about is how channel factories create channels and the concept of channel shapes. That will be the subject of my next posting.

Comments