共用方式為


Using the Base Classes to Build Things that Build Channels

After seeing the ChannelBase class yesterday for implementing a channel, today's post is about the base classes for implementing the things that build channels.  There is a common ChannelManagerBase class, and then the two ends of the channel split into the ChannelFactoryBase class and ChannelListenerBase class.  Both the factory and listener have a generic and non-generic part, although like I mentioned before, just think about the final generic version you get in the end.  This is exactly mirroring the IChannelManager, IChannelFactory, and IChannelListener interfaces.  Each of these base classes only has a few interesting points so I'm doing them all together rather than my normal approach of spreading them out across multiple days.

 public abstract class ChannelManagerBase : CommunicationObject, IChannelManager, ICommunicationObject, IDisposable, IDefaultCommunicationTimeouts
{
   protected ChannelManagerBase();

   protected abstract TimeSpan DefaultReceiveTimeout { get; }
   protected abstract TimeSpan DefaultSendTimeout { get; }
   public abstract MessageVersion MessageVersion { get; }
   public abstract string Scheme { get; }

   protected void AbortChannels();
   protected IAsyncResult BeginCloseChannels(TimeSpan timeout, AsyncCallback callback, object state);
   protected void CloseChannels(TimeSpan timeout);
   protected void EndCloseChannels(IAsyncResult result);
   public virtual T GetProperty<T>() where T : class;
}

The ChannelManagerBase class represents the shared code between the client and server sides.  The bonus functionality you get here is the ability to shut down all of the channels that came from this factory or listener.  In yesterday's post, I mentioned that channels notify their manager when they're created or destroyed.  This is what the manager does with that information.  The timeout for the close operations is the total time for closing all of the channels.  If time expires or an error occurs, these methods stop immediately without trying to process any more channels.  This seems like a silly observation, but Abort runs from newest to oldest through the list while Close runs from oldest to newest.  I would not rely on that factoid for making any kind of decision.  Pretend like these operations will take the channels in a random order.

 public abstract class ChannelFactoryBase : ChannelManagerBase, IChannelFactory, IChannelManager, ICommunicationObject, IDisposable
{
   protected ChannelFactoryBase();
   protected ChannelFactoryBase(IDefaultCommunicationTimeouts timeouts);

   protected override TimeSpan DefaultCloseTimeout {get; }
   protected override TimeSpan DefaultOpenTimeout {get; }
   protected override TimeSpan DefaultReceiveTimeout {get; }
   protected override TimeSpan DefaultSendTimeout {get; }

   public override T GetProperty<T>() where T : class;
   protected override void OnAbort();
   protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state);
   protected override void OnClose(TimeSpan timeout);
   protected override void OnEndClose(IAsyncResult result);
}

public abstract class ChannelFactoryBase<TChannel> : ChannelFactoryBase, IChannelFactory<TChannel>, IChannelFactory, IChannelManager, ICommunicationObject, IDisposable
{
   public ChannelFactoryBase();
   public ChannelFactoryBase(IDefaultCommunicationTimeouts timeouts);

   public TChannel CreateChannel(EndpointAddress address);
   public TChannel CreateChannel(EndpointAddress address, Uri via);
   protected abstract TChannel OnCreateChannel(EndpointAddress address, Uri via);
   protected void ValidateCreateChannel();
}

Most of these methods are skeletons that either implement closing the factory or consolidate methods on the interface.  Two of the methods actually do something.  One is GetProperty, which will respond to people looking for channel factories by returning this instance.  The other is the ValidateCreateChannel method, which checks that you're in the right state.  Your factory must be in the Opened state to create channels.  You'll get a different error message depending on the wrongness of your state according to the standard rules.  Note that if you create a channel while at the same time disposing the factory, you run some risk of either getting an error message after channel creation or not getting any error message at all depending on the order of events.

 public abstract class ChannelListenerBase : ChannelManagerBase, IChannelListener, IChannelManager, ICommunicationObject, IDisposable
{
   protected ChannelListenerBase();
   protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);

   public abstract Type ChannelType {get; }
   protected override TimeSpan DefaultCloseTimeout {get; }
   protected override TimeSpan DefaultOpenTimeout {get; }
   protected override TimeSpan DefaultReceiveTimeout {get; }
   protected override TimeSpan DefaultSendTimeout {get; }
   public virtual Identity Identity {get; }
   public virtual IChannelListener InnerChannelListener {get; set; }
   public abstract Uri Uri {get; }

   public IAsyncResult BeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state);
   public bool EndWaitForChannel(IAsyncResult result);
   public override T GetProperty<T>() where T : class;
   protected override void OnAbort();
   protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state);
   protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
   protected abstract IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state);
   protected override void OnClose(TimeSpan timeout);
   protected override void OnEndClose(IAsyncResult result);
   protected override void OnEndOpen(IAsyncResult result);
   protected abstract bool OnEndWaitForChannel(IAsyncResult result);
   protected override void OnOpen(TimeSpan timeout);
   protected abstract bool OnWaitForChannel(TimeSpan timeout);
   public bool WaitForChannel(TimeSpan timeout);
}

public abstract class ChannelListenerBase<TChannel> : ChannelListenerBase, IChannelListener<TChannel>, IChannelListener, IChannelManager, ICommunicationObject, IDisposable where TChannel : class, System.ServiceModel.Channels.IChannel
{
   protected ChannelListenerBase();
   protected ChannelListenerBase(bool sharedInnerListener);
   protected ChannelListenerBase(IDefaultCommunicationTimeouts timeouts);
   protected ChannelListenerBase(bool sharedInnerListener, IDefaultCommunicationTimeouts timeouts);

   public override Type ChannelType { get; }

   public TChannel AcceptChannel();
   public TChannel AcceptChannel(TimeSpan timeout);
   public IAsyncResult BeginAcceptChannel(AsyncCallback callback, object state);
   public IAsyncResult BeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state);
   public TChannel EndAcceptChannel(IAsyncResult result);
   protected abstract TChannel OnAcceptChannel(TimeSpan timeout);
   protected abstract IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state);
   protected abstract TChannel OnEndAcceptChannel(IAsyncResult result);
}

Whew, there are even more skeleton methods on the listener.  Like the factory, your listener must be in the Opened state to open new channels.  The other statements about GetProperty, error messages, and opening channels while closing the listener all apply as well.  Unlike the factory, the listener is actively doing something even when you're not calling methods.  The next time you try to get a channel from the listener, you'll get any exceptions acquired in the meantime.

A point that I want to make here is that you absolutely should not use InnerChannelListener.  It should stand out as something really strange because you have a setter for that property, which can't possibly result in good things if you try to use it.  The correct way to find one of your inner channels is through the GetProperty method, and it's never correct to try to alter the channel stack like that after creation.

Next time: How TCP/IP Works, Part 1: Layers

Comments

  • Anonymous
    April 06, 2006
    After a bit of a diversion, let's spend some time again looking at the parts necessary to build a custom...
  • Anonymous
    April 18, 2006
    During the last few weeks I've been going over various parts of the channel model and giving some commentary...
  • Anonymous
    May 24, 2006
    Today's post is a light entr&#233;e covering the IDefaultCommunicationTimeouts interface.  This interface...