Building a Custom File Transport, Part 8: Channel Listener


The client is actually done at this point. I don’t know how many people have actually tried running the code or if you’re all just following along. If you did try running the client, you would see a file appear on your hard drive, probably at c:\x\request. Inside that file would be a SOAP message representing the client request. Fabulous. Now we just need to write the rest of the server so that something actually happens in response to that message.

The server has three classes left to write. We’ll need the channel listener, which corresponds to the channel factory on the client side. We’ll need the reply channel, which corresponds to the request channel on the client side. Finally, we’ll need the request context. That piece is entirely unique to the server and is what correlates request messages with replies.

First up is the channel listener. This class is remarkably similar to the channel factory except for the methods that allow you to asynchronously receive channels back from the listener. Since the example doesn’t support asynchronous operations, that means the listener and factory are pretty much identical in terms of code.

using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace FileTransport
{
   class FileReplyChannelListener : ChannelListenerBase<IReplyChannel>
   {
      readonly BufferManager bufferManager;
      readonly MessageEncoderFactory encoderFactory;
      public readonly long MaxReceivedMessageSize;
      readonly string scheme;
      public readonly bool Streamed;
      readonly Uri uri;

      public FileReplyChannelListener(FileTransportBindingElement transportElement, BindingContext context)
         : base(context.Binding)
      {
         MessageEncodingBindingElement messageEncodingElement = context.UnhandledBindingElements.Remove<MessageEncodingBindingElement>();
         this.bufferManager = BufferManager.CreateBufferManager(transportElement.MaxBufferPoolSize, int.MaxValue);
         this.encoderFactory = messageEncodingElement.CreateMessageEncoderFactory();
         MaxReceivedMessageSize = transportElement.MaxReceivedMessageSize;
         this.scheme = transportElement.Scheme;
         Streamed = transportElement.Streamed;
         this.uri = new Uri(context.ListenUriBaseAddress, context.ListenUriRelativeAddress);
      }

      protected override void OnOpen(TimeSpan timeout)
      {
         base.OnOpen(timeout);
         Directory.CreateDirectory(Uri.AbsolutePath);
      }

      protected override IReplyChannel OnAcceptChannel(TimeSpan timeout)
      {
         EndpointAddress address = new EndpointAddress(Uri);
         return new FileReplyChannel(this.bufferManager, this.encoderFactory, address, this);
      }

      protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      protected override IReplyChannel OnEndAcceptChannel(IAsyncResult result)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      protected override IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      protected override bool OnEndWaitForChannel(IAsyncResult result)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      protected override bool OnWaitForChannel(TimeSpan timeout)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public override Uri Uri
      {
         get { return this.uri; }
      }

      public override MessageVersion MessageVersion
      {
         get { return MessageVersion.Default; }
      }

      public override string Scheme
      {
         get { return this.scheme; }
      }
   }
}

Next time: Building a Custom File Transport, Part 9: Reply Channel

Comments (8)

  1. I’ve been providing the contents for this article in bits and pieces but now the whole thing is assembled…

  2. Today’s article is just a summary of what we’ve put together with the file transport and a demonstration…

  3. damir says:

    Hi Allen,

    I’m implementing the custom transport channel, which has to utilize IDuplexSessionChannel shape. I also read lot of your posts and probably, you should the right guy who can answer my questions.

    Imagine, there is a requirement to change your FileReplyChannelListener : ChannelListenerBase<IReplyChannel> to utilize the duplex-pattern as follows:

    public class MyTransportListener : ChannelListenerBase<IDuplexSessionChannel>

    I tried to do that and run into many, many questions.

    First of all I am looking for proposed pattern for implementing of any of methods

    BeginSomething(TimeSpan, AsyncCallback, object) and

    ISomething EndSomething(IAsyncResult)

    For example OnBeginAcceptChannel, OnEndAcceptChannel, BeginTryRaceive and EndTryReceive.

    How often the BeginAcceptChannel will be called?

    How often the BeginTryReceive will be called?

    I assume that dispatching of received messages on the higher level has to be done EndTryReceive in. If so, what is the way to implement continuous receiving? One ofter one message is dispatched by EndTryReceive the BeginTryRecive has to be called again?

  4. damir says:

    Hi Allen,

    I tried your FileChannelTransport example and found out that instead of method OnAccptChannel the method OnBeginAcceptChannel is invoked.

    Who and when decides which of "accept"-methods will be invoked?

    Is that dependent on contract?

    If so, here is the contract I used in your example:

    [ServiceContract]

    public interface ITestService

    {

    [OperationContract]

     string Hello(string message);

    }

  5. Nicholas Allen says:

    Asynchronous programming is actually a pretty big topic to cover.  I’ll have some posts on it in the future, but let me get you started with a few resources.  First, you’ll want to read this general overview of the BeginXXX and EndXXX pattern for programming.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpovrAsynchronousProgrammingOverview.asp

    This pattern appears in a lot of places in the .NET framework so having the big picture is reusable.

    As far as implementing async operations in a channel, I would recommend checking out our UDP transport sample in the Windows Platform SDK.  You may have to dig around a bit to find the WCF samples.  This sample implements the async methods and includes all of the helper classes you’ll need to do this.

    In terms of when methods get called, it’s a long cascade from your service down to the transport level.  The top level will get triggered by either a sync or async call and that style tends to get used all the way down the stack.  The service host dispatcher is a common trigger for these calls since it basically runs in a loop at the top level taking messages until the service quotas are reached.  I think that the service host has a setting to indicate whether sync or async methods will be called (default async) although I can’t remember the setting name off the top of my head.  When you program against the channel layer you of course are in total control of whether you call the sync or async version.

  6. damir says:

    Allen, thanks for the quick answer. My question was more related to the “game” between service model runtime and BeginXXX/EndXXX methods. First thing, which confuses me is that all examples implement sync pattern only.

    However, by using of service model the default pattern is, as you wrote, async-pattern.

    I use following pattern:

    Create Binding

    CreateHost(A,B,C)

    OpenHost()

    What I would like to have is a recommendation how to deal with channels in service model context?

    How channels should be created and how many of them, should generally depend on the connection state etc?

    I think the fully implemented (with BeginXXX/EndXXX) working FileTransportChannel with service and client example would help.

  7. Nicholas Allen says:

    Most samples only implement the sync case because it is much less code to do so.  Handling async calls makes the sample five times as large and people have a hard time reading through it.  We are trying to expand more of our samples to include async support as a demonstration.  Currently, the best one is the UDP transport in the Windows SDK.  The readme for that sample talks about many of these issues.

    In general, the transport author shouldn’t worry too much about when channels are created.  The transport creates a channel when a higher layer (typically the service host) asks for one.  The service host will decide how many channels it should have at once and when to accept a new one.  The transport basically needs to provide two things.  One is to provide a place for deferring pending connection attempts by client.  The other is to accept one of the deferred connections or wait for a new connection to arrive when the higher layer asks for a channel.

  8. I’ve been providing the contents for this article in bits and pieces but now the whole thing is assembled