The Receiver: Channel Listeners


As their name implies, channel listeners do more than simply create channels; they listen for incoming connections. This model is borrowed from the Berkeley Sockets application programming interface (API). In Microsoft Windows programming, this model is visible in the Windows Sockets (Winsock) API. In Microsoft .NET Framework programming, this model is visible in the System.Net.Sockets namespace. In this model, a TcpListener or Socket binds to an address and then passively listens for incoming connections. When a connection becomes available (for example, a client connects to the listener), a method that begins with the word Accept returns an instance of a connected Socket or TcpClient, and the application can use that object to receive data.

In WCF, a channel listener performs the same sort of work. Channel listeners bind to a URI, begin passively waiting for incoming connections, and when a connection becomes available, a method that begins with the word Accept returns an instance of a channel. The application then uses the returned channel to receive a Message. Although all channel listeners define Accept methods, transport channel listeners are the only types of listeners that actually listen for incoming connections. As an example, think of a stack of channel listeners. Like a channel stack, a channel listener stack is ordered in such a way that the transport channel listener is at the bottom of the channel listener stack. The transport channel listener is the only channel listener that binds to an address and begins listening for connections. Channel listeners higher in the channel listener stack simply delegate their Accept method calls to the transport channel listener, as illustrated in Figure 7-1.

image from book
Figure 7-1: The channel listener stack

Not all transport channel listeners behave the same way. Their differences are, in large measure, due to the intrinsic differences between transports. For example, transport channel listeners for connection-oriented transports (for example, TCP/IP and named pipes) return a channel when that listener receives an incoming connection. Transport channel listeners for disconnected transports (for example, MSMQ) return a channel immediately because there is no incoming connection to wait for.

The IChannelListener Interface

All channel listeners implement the System.ServiceModel.Channels.IChannelListener interface. This interface is the type that forces all channels to implement the channel layer state machine and some basic channel listener members. The IChannelListener interface is shown here:

 public interface IChannelListener : ICommunicationObject {     IAsyncResult BeginWaitForChannel(TimeSpan timeout,                                      AsyncCallback callback,                                      Object state);     Boolean EndWaitForChannel(IAsyncResult result);     Boolean WaitForChannel(TimeSpan timeout);     T GetProperty<T>() where T: class;     // the listening address     Uri Uri { get; } }

The WaitForChannel method (and the asynchronous variant) is intended to return a Boolean indicating whether a channel is available. The Uri property is a way to access the listening address. The GetProperty<T> method is identical in structure and intended use to the one implemented in the IChannel interface. The IChannelListener interface does not implement the IChannel interface, because the IChannel interface is a means to identify a channel in the API. For example, many generic parameters are constrained to class and IChannel. The intention is to constrain that parameter to a channel that implements a particular shape. If IChannelListener implemented the IChannel interface, a channel listener type could be used in places otherwise reserved for a channel. However, channel listeners exist in a stack, and that stack must allow queries.

The IChannelListener<TChannel> Interface

All channel listeners also implement the IChannelListener<TChannel> interface. It is in this interface that we first see the Accept paradigm borrowed from the Berkeley Sockets API, shown here:

 public interface IChannelListener<TChannel> : IChannelListener,                                               where TChannel: class,                                                               IChannel {   TChannel AcceptChannel();   TChannel AcceptChannel(TimeSpan timeout);   IAsyncResult BeginAcceptChannel(AsyncCallback callback, Object state);   IAsyncResult BeginAcceptChannel(TimeSpan timeout,                                   AsyncCallback callback,                                   Object state);   TChannel EndAcceptChannel(IAsyncResult result); }

Notice that the interface definition constrains the TChannel generic parameter to a concrete type that implements the IChannel interface. In the WCF API, channels that implement a particular shape meet this criterion. Taken as a whole, this means that a channel listener must reference a particular channel shape. This is subtly but distinctly different from the way channels use channel shapes. Channels implement a channel shape; channel listeners reference a channel shape and use that reference to build a channel that implements that shape.

When closed, a type implementing the IChannelListener<TChannel> interface returns instances of a channel that implements that shape via the AcceptChannel methods (and the asynchronous variants). As with several other members in the channel layer, there is an overloaded AcceptChannel method that accepts a TimeSpan parameter. Because receiving applications often need to listen passively for an indefinite length of time, the value of this argument is often TimeSpan.MaxValue.

The ChannelListenerBase Type

All channel listeners derive from the System.ServiceModel.Channels.ChannelListenerBase abstract type. The type definition for the ChannelListenerBase type is shown here:

 public abstract class ChannelListenerBase : ChannelManagerBase,                                             IChannelListener,                                             ICommunicationObject {   protected ChannelListenerBase();   protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);   // IChannelListener implementation   public IAsyncResult BeginWaitForChannel(TimeSpan timeout,                                           AsyncCallback callback,                                           Object state);   public bool EndWaitForChannel(IAsyncResult result);   public bool WaitForChannel(TimeSpan timeout);   // Extensibility points for IChannelListener members   protected abstract IAsyncResult OnBeginWaitForChannel(TimeSpan timeout,     AsyncCallback callback, Object state);   protected abstract bool OnEndWaitForChannel(IAsyncResult result);   protected abstract bool OnWaitForChannel(TimeSpan timeout);      public abstract Uri Uri { get; }   // Query mechanism   public virtual T GetProperty<T>() where T: class;   // CommunicationObject timeouts   protected override TimeSpan DefaultCloseTimeout { get; }   protected override TimeSpan DefaultOpenTimeout { get; }   // ChannelManagerBase timeouts   protected override TimeSpan DefaultReceiveTimeout { get; }   protected override TimeSpan DefaultSendTimeout { get; } }

The constructor that accepts a TimeSpan is fairly interesting. As a result of the type hierarchy of the ChannelListenerBase type, it defines four protected TimeSpan properties. The WCF type system defaults each of these time-outs to one minute. If that is not acceptable for a channel listener (and the subsequent channels), you can pass an IDefaultCommunicationTimeouts to the constructor of the ChannelListenerBase. In the constructor, the time-outs from this type are assigned to the fields that back the TimeSpan properties. As you’ll see in Chapter 8, “Bindings,” a Binding implements the IDefaultCommunicationTimeouts interface, and this is indeed the means by which time-outs are moved from user code down to the channel layer.

The ChannelListenerBase<TChannel> Type

Channel listeners subclass the System.ServiceModel.Channels.ChannelListenerBase<TChannel> abstract type. This type derives from the ChannelListenerBase type and implements the IChannelListener<TChannel> type, as shown here:

 public abstract class ChannelListenerBase<TChannel> : ChannelListenerBase,     IChannelListener<TChannel>, where TChannel: class, IChannel {   protected ChannelListenerBase();   protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);   // IChannelListener<TChannel> implementation   public IAsyncResult BeginAcceptChannel(AsyncCallback callback,                                          Object state);   public IAsyncResult BeginAcceptChannel(TimeSpan timeout,     AsyncCallback callback, Object state);   public TChannel EndAcceptChannel(IAsyncResult result);   public TChannel AcceptChannel();   public TChannel AcceptChannel(TimeSpan timeout);   // extensibility points for IChannelListener<TChannel>   protected abstract TChannel OnAcceptChannel(TimeSpan timeout);   protected abstract IAsyncResult OnBeginAcceptChannel(TimeSpan timeout,     AsyncCallback callback, Object state);   protected abstract TChannel OnEndAcceptChannel(IAsyncResult result); }

Building a Custom Channel Listener

Now that you’ve seen the types used in channel listeners, let’s create our own. In the previous chapter, you learned how to build several different DelegatorChannel channels. In this section, you’ll see how to build a channel listener that creates these channels on a receiving application. As mentioned at the outset of this chapter, this sample will not work on its own until the conclusion of Chapter 8.

When building a channel listener, one has to consider the shape of the channel that the channel listener is going to build. Because our DelegatorChannel example can be of any shape, our channel listener must be able to create all known DelegatorChannel channels. In Chapter 6, we used generic parameters as a means to provide this type of flexibility, and we will do so again in this example.

Let’s start with what we know. We know that the easiest way to create a channel listener is to derive a type from ChannelListenerBase<TChannel>. We also know that we need to make our channel listener generic, and this will allow our channel listener to work with the different possible channel shapes. With this in mind, our channel listener definition looks like the following:

 internal sealed class DelegatorChannelListener<TShape> :   ChannelListenerBase<TShape> where TShape : class, IChannel {   // implementation omitted for clarity }

Notice the access modifier of the DelegatorChannelListener<TShape> type. Like the channel definitions shown in Chapter 6, this channel listener does not need to be accessible to outside callers. We will provide that accessibility in Chapter 8, through the Binding and BindingElement objects. Now that we have the name and base type of our channel listener, let’s add the implementation. The following is a full implementation of the DelegatorChannelListener<TShape> type:

 internal sealed class DelegatorChannelListener<TShape> :     ChannelListenerBase<TShape> where TShape : class, IChannel {   // field referencing the next channel listener   IChannelListener<TShape> _innerListener;   // String to output to console   String _consolePrefix = "LISTENER: DelegatorChannelListener";   // builds the next channel listener, then assigns it to   // the _innerListener field   public DelegatorChannelListener(BindingContext context) {     PrintHelper.Print(_consolePrefix, "ctor");     this._innerListener = context.BuildInnerChannelListener<TShape>();   }   // Creates a DelegatorChannel of the correct shape and returns it   private TShape WrapChannel(TShape innerChannel) {     if(innerChannel == null) {       throw new ArgumentNullException("innerChannel cannot be null", "innerChannel");     }     if(typeof(TShape) == typeof(IInputChannel)) {       return (TShape)(Object)new DelegatorInputChannel<IInputChannel>(this, (IInputChannel)innerChannel, "RECEIVE");     }     if(typeof(TShape) == typeof(IReplyChannel)) {       return (TShape)(object)new DelegatorReplyChannel(this, (IReplyChannel)innerChannel, "RECEIVE");     }     if(typeof(TShape) == typeof(IDuplexChannel)) {      return (TShape)(object)new DelegatorDuplexChannel(this, (IDuplexChannel)innerChannel, "RECEIVE");     }     if(typeof(TShape) == typeof(IInputSessionChannel)) {       return (TShape)(object)new DelegatorInputSessionChannel(this, (IInputSessionChannel)innerChannel, "RECEIVE");     }     if(typeof(TShape) == typeof(IReplySessionChannel)) {       return (TShape)(object)new DelegatorReplySessionChannel(this, (IReplySessionChannel)innerChannel, "RECEIVE");     }     if(typeof(TShape) == typeof(IDuplexSessionChannel)) {       return (TShape)(object)new DelegatorDuplexSessionChannel(this, (IDuplexSessionChannel)innerChannel, "RECEIVE");     }     // cannot wrap this channel     throw new ArgumentException(String.Format("invalid channel shape passed:{0}", innerChannel.GetType()));   }      // IChannelListener<TChannel> members   protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state) {     PrintHelper.Print(_consolePrefix, "OnBeginAcceptChannel");     return this._innerListener.BeginAcceptChannel(timeout, callback, state);   }   protected override TShape OnEndAcceptChannel(IAsyncResult result) {     // create and return the channel     PrintHelper.Print(_consolePrefix, "OnEndAcceptChannel");     TShape innerChannel = _innerListener.EndAcceptChannel(result);     // when closing, _inner.EndAcceptChannel returns null, nothing to wrap     if (innerChannel != null) {       return WrapChannel(innerChannel);     }     return null;   }      protected override TShape OnAcceptChannel(TimeSpan timeout){     // delegate to next channel, wrap it, and return it     PrintHelper.Print(_consolePrefix, "OnAcceptChannel");     TShape innerChannel = _innerListener.AcceptChannel(timeout);     // when closing, _inner.AcceptChannel returns null, nothing to wrap     if (innerChannel != null) {       return WrapChannel(innerChannel);     }     return null;   }   // IChannelListener members   protected override IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state) {     PrintHelper.Print(_consolePrefix, "OnBeginWaitForChannel");     return this._innerListener.BeginWaitForChannel(timeout, callback, state);   }   protected override bool OnEndWaitForChannel(IAsyncResult result) {     PrintHelper.Print(_consolePrefix, "OnEndWaitForChannel");     return this._innerListener.EndWaitForChannel(result);   }   protected override bool OnWaitForChannel(TimeSpan timeout) {     PrintHelper.Print(_consolePrefix, "OnWaitForChannel");     return this._innerListener.WaitForChannel(timeout);   }   public override Uri Uri {     get {       PrintHelper.Print(_consolePrefix, "Uri");       return this._innerListener.Uri;     }   }   public override T GetProperty<T>() {     PrintHelper.Print(_consolePrefix, "GetProperty<" + typeof(T) + ">");     return this._innerListener.GetProperty<T>();   }   // CommunicationObject members   protected override void OnAbort() {     PrintHelper.Print(_consolePrefix, "OnAbort");     this._innerListener.Abort();   }   protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) {     PrintHelper.Print(_consolePrefix, "OnBeginClose");     return this._innerListener.BeginClose(timeout, callback, state);   }    protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) {     PrintHelper.Print(_consolePrefix, "OnBeginOpen");     return this._innerListener.BeginOpen(timeout, callback, state);   }   protected override void OnClose(TimeSpan timeout) {     PrintHelper.Print(_consolePrefix, "OnClose");     this._innerListener.Close(timeout);   }   protected override void OnEndClose(IAsyncResult result) {     PrintHelper.Print(_consolePrefix, "OnEndClose");     this._innerListener.EndClose(result);   }   protected override void OnEndOpen(IAsyncResult result) {     PrintHelper.Print(_consolePrefix, "OnEndOpen");     this._innerListener.EndOpen(result);   }      protected override void OnOpen(TimeSpan timeout) {     PrintHelper.Print(_consolePrefix, "OnOpen");     this._innerListener.Open(timeout);   } }

A few parts of this type require some explanation. Let’s start with the constructor. Like the DelegatorChannel definitions in the previous chapter, DelegatorChannelListener<TShape> objects exist in a stack with other channel listeners. There are several ways to build a channel listener stack, but in the end, the result must be a stack of channel listeners with the transport channel listener at the bottom of the stack. The DelegatorChannelListener<TShape> type defines a member variable of type IChannelListener<TShape> and assigns that member variable in the constructor via a constructor parameter. As you’ll see in Chapter 8, the BindingContext object used at run time by a Binding is the primary way to build the channel listener stack. Another viable approach is to make the constructor parameter of type IChannelListener<TShape>. This offloads the responsibility of using the BindingContext to the caller. In my view, the difference between these two approaches is not substantive.

Most of the methods in the DelegatorChannelListener<TShape> are conceptually similar to the DelegatorChannel channels in that they simply delegate to the next channel listener in the channel listener stack. One interesting method in the DelegatorChannelListener<TShape> type is the WrapChannel private method. As indicated in the comments, the purpose of this method is to return an instance of a DelegatorChannel that has the same shape as the TShape generic parameter. The innerChannel parameter is passed to the constructor of the DelegatorChannel so that the channel stack can be built properly. The OnAcceptChannel and OnEndAcceptChannel methods are the only methods that call the WrapChannel method. Before these methods can call the WrapChannel method, however, they must call the appropriate method on the _innerListener member variable (AcceptChannel and EndAcceptChannel, respectively) and then pass the channel listener to the WrapChannel method.

When the channel listener stack is closing, the DelegatorChannelListener<TShape> type delegates the closing calls (for example, Close, OnClose, Abort, OnAbort) to the next channel listener in the channel listener stack. If the BeginAcceptChannel or AcceptChannel method was called prior to the closing method call, the delegated calls will return null. In this case, it is important that the OnEndAcceptChannel or AcceptChannel method return null also.




Inside Windows Communication Foundation
Inside Windows Communication Foundation (Pro Developer)
ISBN: 0735623066
EAN: 2147483647
Year: 2007
Pages: 106
Authors: Justin Smith

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net