Building A Custom Message Encoder to Record Throughput, Part 3

After a short break we're back to working on the custom message encoder. The complete source code for the encoder is available in Part 1 and Part 2 of this series. Today and tomorrow I'll be performing some runs using the encoder to show how it works. We'll need a sample client and server application to host the encoder. I've dusted off the code I put together for the FileTransport to fill this role.

 using System;
using System.ServiceModel.Channels;
using CountingEncoder;
using FileTransport;

namespace Server
{
   class Server
   {
      static void Main(string[] args)
      {
         Console.Write("Creating listener...");
         CountingEncoderBindingElement encoder = new CountingEncoderBindingElement(new TextMessageEncodingBindingElement());
         FileTransportBindingElement transport = new FileTransportBindingElement();
         transport.Streamed = true;
         CustomBinding binding = new CustomBinding(encoder, transport);
         Uri uri = new Uri("my.file://localhost/x");
         IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(uri, new BindingParameterCollection());
         listener.Open(TimeSpan.FromSeconds(5));
         Console.WriteLine(" done.");
         Console.Write("Creating channel...");
         IReplyChannel channel = listener.AcceptChannel(TimeSpan.FromSeconds(5));
         channel.Open(TimeSpan.FromSeconds(5));
         Console.WriteLine(" done.");
         Console.Write("Waiting for request...");
         while (channel.WaitForRequest(TimeSpan.FromMinutes(1)))
         {
            using (IRequestContext context = channel.ReceiveRequest(TimeSpan.FromSeconds(5)))
            {
               Console.WriteLine(" done.");
               using (Message message = context.RequestMessage)
               {
                  Console.WriteLine("Processing request: {0}", message.Headers.Action);
                  if (message.Headers.Action == "reflect")
                  {
                     string response = ProcessReflectRequest(message.GetBody<string>());
                     Console.Write("Sending reply...");
                     Message replyMessage = Message.CreateMessage(MessageVersion.Default, "reflection", response);
                     context.Reply(replyMessage, TimeSpan.FromSeconds(5));
                     Console.WriteLine(" done.");
                  }
               }
            }
            Console.Out.WriteLine("Read {0} bytes in {1} operations.", encoder.ReadBytes, encoder.ReadCount);
            Console.Out.WriteLine("Wrote {0} bytes in {1} operations.", encoder.WriteBytes, encoder.WriteCount);
            Console.Write("Waiting for request...");
         }
         Console.WriteLine(" terminated.");
         channel.Close(TimeSpan.FromSeconds(5));
      }

      static string ProcessReflectRequest(string request)
      {
         char[] output = new char[request.Length];
         for (int index = 0; index < request.Length; index++)
         {
            output[index] = request[request.Length - index - 1];
         }
         return new string(output);
      }
   }
}
 using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using CountingEncoder;
using FileTransport;

namespace Client
{
   class Client
   {
      static void Main(string[] args)
      {
         Console.Write("Creating factory...");
         CountingEncoderBindingElement encoder = new CountingEncoderBindingElement(new TextMessageEncodingBindingElement());
         FileTransportBindingElement transport = new FileTransportBindingElement();
         transport.Streamed = true;
         CustomBinding binding = new CustomBinding(encoder, transport);
         IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>();
         factory.Open(TimeSpan.FromSeconds(5));
         Console.WriteLine(" done.");
         Console.Write("Creating channel...");
         using (factory)
         {
            Uri uri = new Uri("my.file://localhost/x");
            IRequestChannel channel = factory.CreateChannel(new EndpointAddress(uri));
            Console.WriteLine(" done.");
            Console.Write("Enter some text: ");
            String text = Console.ReadLine();
            if (text == null)
            {
               return;
            }
            Console.Write("Sending request...");
            Message requestMessage = Message.CreateMessage(MessageVersion.Default, "reflect", text);
            channel.Open(TimeSpan.FromSeconds(5));
            Message replyMessage = channel.Request(requestMessage, TimeSpan.FromSeconds(5));
            Console.WriteLine(" done.");
            using (replyMessage)
            {
               Console.WriteLine("Processing reply: {0}", replyMessage.Headers.Action);
               Console.WriteLine("Reply: {0}", replyMessage.GetBody<string>());
            }
            channel.Close(TimeSpan.FromSeconds(5));
         }
         Console.Out.WriteLine("Read {0} bytes in {1} operations.", encoder.ReadBytes, encoder.ReadCount);
         Console.Out.WriteLine("Wrote {0} bytes in {1} operations.", encoder.WriteBytes, encoder.WriteCount);
      }
   }
}

In the code here, I've created a binding with the file transport and text message encoder. I've set the transfer mode to use streaming for this example. The counting encoder wraps around the text encoder to inspect the bytes as they go to the transport. After completing an operation, the client and server both print the number of bytes they sent and received as well as the number of individual calls required.

Here's what I get when running this on the server:

 Creating listener... done.
Creating channel... done.
Waiting for request... done.
Processing request: reflect
Sending reply... done.
Read 352 bytes in 8 operations.
Wrote 355 bytes in 1 operations.
Waiting for request...

And, here's what I get when running this on the client:

 Creating factory... done.
Creating channel... done.
Enter some text: abcd
Sending request... done.
Processing reply: reflection
Reply: dcba
Read 355 bytes in 8 operations.
Wrote 352 bytes in 1 operations.

Notice that a single send operation has eight read operations on the other side of the connection. The way that the data is framed and written by the send side of the connection does not put any requirements on how the receive side must consume the data. Some transports have this kind of requirement at the network level, but you generally cannot see any impact in your application.

Next time: Building A Custom Message Encoder to Record Throughput, Part 4