次の方法で共有


WCF Extensibility – Transport Channels – Request Channels, part 3

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page .

This is the part 3 of 3 of a “mini-series” inside the main series. For the other parts, here’s the list.

Just like we did on the previous post, let’s jump right back into the scenario (for a scenario description, go back to the part 1). After the detour through the service model, we’ll dive back to the transport channel, to implement the asynchronous support for the request channel used to consume the service. I’ll also share some ideas for testing such operations.

Real world scenario: consuming a JSON-RPC service

At the previous post, we had a transport channel plus an endpoint behavior, which, together, could be used to call a simple JSON-RPC service. Now, let’s finish the implementation of the request channel with support for asynchronous requests. This is important in scenarios such as when that transport is to be used in UI threads (so we don’t block that thread and make the application “hang” while the request is being made), or in platforms such as Silverlight, in which synchronous networking requests simply don’t exist.

Since we’re going to enter the asynchronous world, testing each part of the communication as it’s implemented is really important. I’ve made the mistake in the past of trying to implement all the asynchronous operations, then trying to use it within a WCF channel. Things would blow up in a worker thread, and tracking down what was going on after all the asynchronous callbacks wasn’t something fun. So I strongly recommend to have some sort of unit test framework which can be used to validate the individual pieces of the system. I’d usually go with MSTest (the unit test framework which comes with Visual Studio), but since at home I use the express editions and it’s not supported, I decided to go with xUnit (one I’m use to and it’s free / open source; others would also be good) for this.

But before going further, here goes the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). I really kept the error checking to a minimum (especially in the “simple service” project and in many other helper functions whose inputs should be sanitized, such as the reflection helper), to make the sample small (or as small as a fully synchronous and asynchronous transport channel can be). Also, the timeout handling in this sample is far from ideal (in some cases they’re simply ignored, in other they’re interpreted very liberally).

So before I even attempted to write the first asynchronous operation, I started a simple unit test project. And since most of the operations (and the base class itself) were private / internal, I used a simple reflection helper to test the internal methods, as shown below.

  1. static class ReflectionHelper
  2. {
  3.     public static object CreateInstance(Type publicTypeFromAssembly, string typeName,
  4.         params object[] constructorArgs)
  5.     {
  6.         Type type = publicTypeFromAssembly.Assembly.GetType(typeName);
  7.         ConstructorInfo ctor = FindConstructor(type, constructorArgs);
  8.         return ctor.Invoke(constructorArgs);
  9.     }
  10.  
  11.     public static object CallMethod(object obj, string methodName, params object[] methodArgs)
  12.     {
  13.         MethodInfo method = FindMethod(obj.GetType(), methodName, methodArgs);
  14.         return method.Invoke(obj, methodArgs);
  15.     }
  16.  
  17.     public static void SetField(object obj, string fieldName, object fieldValue)
  18.     {
  19.         FieldInfo field = FindField(obj.GetType(), fieldName);
  20.         field.SetValue(obj, fieldValue);
  21.     }
  22.  
  23.     public static object GetField(object obj, string fieldName)
  24.     {
  25.         FieldInfo field = FindField(obj.GetType(), fieldName);
  26.         return field.GetValue(obj);
  27.     }
  28. }

In order to test the socket operations, I also wrote a simple socket server which simply returned whatever data was sent to it (according to the same framing protocol, 4-byte length + data, as the transport channel), to validate our client implementation. I’ll leave the simple server out of this post, but it is in the code from the gallery. Also, to test socket operations we need both send / receive, so to have a starting point for the test, the first test I wrote didn’t use anything asynchronous at all, was a simple synchronous send/receive (it was useful to test my simple server as well).

  1. [Fact]
  2. public void SynchronousSendReceive()
  3. {
  4.     byte[] length = new byte[4];
  5.     byte[] data = new byte[36];
  6.     Random rndGen = new Random();
  7.     rndGen.NextBytes(data);
  8.     Formatting.SizeToBytes(data.Length, length, 0);
  9.     byte[] toSend = new byte[length.Length + data.Length];
  10.     Array.Copy(length, 0, toSend, 0, length.Length);
  11.     Array.Copy(data, 0, toSend, length.Length, data.Length);
  12.  
  13.     object channel = ReflectionHelper.CreateInstance(
  14.         typeof(SizedTcpTransportBindingElement),
  15.         "JsonRpcOverTcp.Channels.SizedTcpBaseChannel",
  16.         null,
  17.         BufferManager.CreateBufferManager(int.MaxValue, int.MaxValue),
  18.         Mocks.GetChannelManagerBase());
  19.     Socket socket = Mocks.GetConnectedSocket(Port);
  20.     ReflectionHelper.SetField(channel, "socket", socket);
  21.  
  22.     ReflectionHelper.CallMethod(channel, "SocketSend", toSend);
  23.     ReflectionHelper.CallMethod(channel, "SocketReceiveBytes", toSend.Length);
  24.  
  25.     Assert.Equal(2, this.server.ReceivedBytes.Count);
  26.     Assert.Equal(length, this.server.ReceivedBytes[0], new ArrayComparer<byte>());
  27.     Assert.Equal(data, this.server.ReceivedBytes[1], new ArrayComparer<byte>());
  28.     socket.Close();
  29. }

Having that test ready gave me more confidence that the synchronous operations were working, so I could start testing the asynchronous ones individually (i.e., first test / implement the asynchronous Send, and test it with the synchronous receive, which I “knew” was working). And here goes the first test prior to the implementation (in a TDD fashion which I should have used from the start, but that’s another story). The test is exactly like the previous one, but the SocketSend method is split in a Begin/End pair. A simple event is used to wait in the main thread until the asynchronous part is done.

  1. [Fact]
  2. public void AsynchronousSendSynchronousReceive()
  3. {
  4.     byte[] length = new byte[4];
  5.     byte[] data = new byte[36];
  6.     Random rndGen = new Random();
  7.     rndGen.NextBytes(data);
  8.     Formatting.SizeToBytes(data.Length, length, 0);
  9.     byte[] toSend = new byte[length.Length + data.Length];
  10.     Array.Copy(length, 0, toSend, 0, length.Length);
  11.     Array.Copy(data, 0, toSend, length.Length, data.Length);
  12.  
  13.     ManualResetEvent evt = new ManualResetEvent(false);
  14.     object channel = ReflectionHelper.CreateInstance(
  15.         typeof(SizedTcpTransportBindingElement),
  16.         "JsonRpcOverTcp.Channels.SizedTcpBaseChannel",
  17.         null,
  18.         BufferManager.CreateBufferManager(int.MaxValue, int.MaxValue),
  19.         Mocks.GetChannelManagerBase());
  20.     Socket socket = Mocks.GetConnectedSocket(Port);
  21.     ReflectionHelper.SetField(channel, "socket", socket);
  22.  
  23.     object state = new object();
  24.     bool success = true;
  25.     ReflectionHelper.CallMethod(channel, "BeginSocketSend", toSend, new AsyncCallback(delegate(IAsyncResult asyncResult)
  26.     {
  27.         try
  28.         {
  29.             if (!Object.ReferenceEquals(asyncResult.AsyncState, state))
  30.             {
  31.                 success = false;
  32.                 Console.WriteLine("Error, state not preserved");
  33.             }
  34.             else
  35.             {
  36.                 ReflectionHelper.CallMethod(channel, "EndSocketSend", asyncResult);
  37.                 ReflectionHelper.CallMethod(channel, "SocketReceiveBytes", toSend.Length);
  38.  
  39.                 try
  40.                 {
  41.                     Assert.Equal(2, this.server.ReceivedBytes.Count);
  42.                     Assert.Equal(length, this.server.ReceivedBytes[0], new ArrayComparer<byte>());
  43.                     Assert.Equal(data, this.server.ReceivedBytes[1], new ArrayComparer<byte>());
  44.                 }
  45.                 catch (Exception e)
  46.                 {
  47.                     Console.WriteLine("Error: " + e);
  48.                     success = false;
  49.                 }
  50.             }
  51.         }
  52.         finally
  53.         {
  54.             evt.Set();
  55.         }
  56.     }), state);
  57.  
  58.     evt.WaitOne();
  59.     Assert.True(success, "Error in callback");
  60.     socket.Close();
  61. }

And we can now finally start implementing the operations. First, [Begin/End]SocketSend. As will be the pattern in all the implementations here, we’ll create a new class which implements IAsyncResult (actually, a class derived from the helper class AsyncResult, shown in the first post, which in turn implements that interface). And that class will be the one which will do all the work.

  1. IAsyncResult BeginSocketSend(byte[] buffer, AsyncCallback callback, object state)
  2. {
  3.     return BeginSocketSend(new ArraySegment<byte>(buffer), callback, state);
  4. }
  5.  
  6. IAsyncResult BeginSocketSend(ArraySegment<byte> buffer, AsyncCallback callback, object state)
  7. {
  8.     return new SocketSendAsyncResult(buffer, this, callback, state);
  9. }
  10.  
  11. void EndSocketSend(IAsyncResult result)
  12. {
  13.     SocketSendAsyncResult.End(result);
  14. }

The SocketSendAsyncResult class is show in its entirety below. Right at its constructor (after storing some variables), we start the asynchronous operation (via the StartSending) method. In StartSending, we call the Socket.BeginSend to start that. One thing which is often overlooked is that asynchronous operations can actually be completed synchronously, so to prevent race conditions between chained asynchronous calls, it’s recommended that we check that and take different actions based on that. If it’s a “normal” asynchronous operation, we simply return and wait for the callback; otherwise we go into the CompleteSend function to see if the sending is completed, and complete the AsyncResult itself if it’s the case. Otherwise, we start sending it again. On CompleteSend, we need to see if all the bytes which we asked the socket to send were actually sent; if not, we need to start sending again from the point where we stopped. In the asynchronous case, the callback is invoked when the socket operation is complete (it’s also invoked if the operation completed synchronously, but since we already dealt with that case, the first thing in the operation is to check that and return if that’s indeed the case). In the callback we take a similar path as in the “completed synchronously” case: call CompleteSend to see if all bytes were indeed sent, and if not, start sending again.

  1. class SocketSendAsyncResult : AsyncResult
  2. {
  3.     SizedTcpBaseChannel channel;
  4.     ArraySegment<byte> buffer;
  5.  
  6.     public SocketSendAsyncResult(ArraySegment<byte> buffer, SizedTcpBaseChannel channel, AsyncCallback callback, object state)
  7.         : base(callback, state)
  8.     {
  9.         this.channel = channel;
  10.         this.buffer = buffer;
  11.  
  12.         this.StartSending();
  13.     }
  14.  
  15.     void StartSending()
  16.     {
  17.         IAsyncResult sendResult = this.channel.socket.BeginSend(this.buffer.Array, this.buffer.Offset, this.buffer.Count, SocketFlags.None, OnSend, this);
  18.         if (!sendResult.CompletedSynchronously)
  19.         {
  20.             return;
  21.         }
  22.  
  23.         if (this.CompleteSend(sendResult))
  24.         {
  25.             base.Complete(true);
  26.         }
  27.         else
  28.         {
  29.             this.StartSending();
  30.         }
  31.     }
  32.  
  33.     bool CompleteSend(IAsyncResult result)
  34.     {
  35.         try
  36.         {
  37.             int bytesSent = channel.socket.EndSend(result);
  38.             if (bytesSent == this.buffer.Count)
  39.             {
  40.                 return true;
  41.             }
  42.             else
  43.             {
  44.                 this.buffer = new ArraySegment<byte>(this.buffer.Array, this.buffer.Offset + bytesSent, this.buffer.Count - bytesSent);
  45.                 return false;
  46.             }
  47.         }
  48.         catch (SocketException socketException)
  49.         {
  50.             throw ConvertSocketException(socketException, "Send");
  51.         }
  52.     }
  53.  
  54.     static void OnSend(IAsyncResult result)
  55.     {
  56.         if (result.CompletedSynchronously)
  57.         {
  58.             return;
  59.         }
  60.  
  61.         SocketSendAsyncResult thisPtr = (SocketSendAsyncResult)result.AsyncState;
  62.         Exception completionException = null;
  63.         bool shouldComplete = false;
  64.         try
  65.         {
  66.             if (thisPtr.CompleteSend(result))
  67.             {
  68.                 shouldComplete = true;
  69.             }
  70.             else
  71.             {
  72.                 thisPtr.StartSending();
  73.             }
  74.         }
  75.         catch (Exception e)
  76.         {
  77.             completionException = e;
  78.         }
  79.  
  80.         if (shouldComplete)
  81.         {
  82.             thisPtr.Complete(false, completionException);
  83.         }
  84.     }
  85.  
  86.  
  87.     public static void End(IAsyncResult result)
  88.     {
  89.         AsyncResult.End<SocketSendAsyncResult>(result);
  90.     }
  91. }

If you didn’t understand that class, please go back and look at it again. Classes like that will show up a lot over the next paragraphs…

So after validating that the unit test works (I had to fix a few things myself), so that was already a good thing to have those, on to the next operation: socket receive. Just like the socket send case, I’ll start with a unit test to validate it, using the synchronous send operation (which is a lot less error-prone than its asynchronous counterpart).

  1. [Fact]
  2. public void SynchronousSendAsynchronousReceive()
  3. {
  4.     byte[] length = new byte[4];
  5.     byte[] data = new byte[36];
  6.     Random rndGen = new Random();
  7.     rndGen.NextBytes(data);
  8.     Formatting.SizeToBytes(data.Length, length, 0);
  9.     byte[] toSend = new byte[length.Length + data.Length];
  10.     Array.Copy(length, 0, toSend, 0, length.Length);
  11.     Array.Copy(data, 0, toSend, length.Length, data.Length);
  12.  
  13.     object channel = ReflectionHelper.CreateInstance(
  14.         typeof(SizedTcpTransportBindingElement),
  15.         "JsonRpcOverTcp.Channels.SizedTcpBaseChannel",
  16.         null,
  17.         BufferManager.CreateBufferManager(int.MaxValue, int.MaxValue),
  18.         Mocks.GetChannelManagerBase());
  19.     Socket socket = Mocks.GetConnectedSocket(Port);
  20.     ReflectionHelper.SetField(channel, "socket", socket);
  21.  
  22.     ReflectionHelper.CallMethod(channel, "SocketSend", toSend);
  23.     object state = new object();
  24.     bool success = true;
  25.     ManualResetEvent evt = new ManualResetEvent(false);
  26.     ReflectionHelper.CallMethod(channel, "BeginSocketReceiveBytes", toSend.Length, new AsyncCallback(delegate(IAsyncResult asyncResult)
  27.     {
  28.         try
  29.         {
  30.             if (!Object.ReferenceEquals(asyncResult.AsyncState, state))
  31.             {
  32.                 success = false;
  33.                 Console.WriteLine("Error, state not preserved");
  34.             }
  35.             else
  36.             {
  37.                 try
  38.                 {
  39.                     byte[] recvd = (byte[])ReflectionHelper.CallMethod(channel, "EndSocketReceiveBytes", asyncResult);
  40.                     Assert.NotNull(recvd);
  41.                     Assert.True(recvd.Length >= toSend.Length);
  42.                     if (recvd.Length > toSend.Length)
  43.                     {
  44.                         // maybe buffer manager returned a bigger buffer
  45.                         byte[] temp = new byte[toSend.Length];
  46.                         Array.Copy(recvd, 0, temp, 0, temp.Length);
  47.                         recvd = temp;
  48.                     }
  49.  
  50.                     Assert.Equal(recvd, toSend, new ArrayComparer<byte>());
  51.                 }
  52.                 catch (Exception e)
  53.                 {
  54.                     Console.WriteLine("Error: " + e);
  55.                     success = false;
  56.                 }
  57.             }
  58.         }
  59.         finally
  60.         {
  61.             evt.Set();
  62.         }
  63.     }), state);
  64.  
  65.     evt.WaitOne();
  66.     Assert.True(success, "Error in callback");
  67.     socket.Close();
  68. }

And the pattern continues. The Begin/End operations are fairly simple, just delegating the bulk of the work to the new class derived from AsyncResult.

  1. IAsyncResult BeginSocketReceiveBytes(int size, AsyncCallback callback, object state)
  2. {
  3.     return BeginSocketReceiveBytes(size, true, callback, state);
  4. }
  5.  
  6. IAsyncResult BeginSocketReceiveBytes(int size, bool throwOnEmpty, AsyncCallback callback, object state)
  7. {
  8.     return new SocketReceiveAsyncResult(size, throwOnEmpty, this, callback, state);
  9. }
  10.  
  11. byte[] EndSocketReceiveBytes(IAsyncResult result)
  12. {
  13.     return SocketReceiveAsyncResult.End(result);
  14. }

Like on the previous AsyncResult class, the SocketReceiveAsyncResult starts off by storing some internal data, then firing off the socket request. Whether the request completed synchronously or not, the method CompleteReadBytes is called (by the constructor or the OnReadBytes callback, respectively). There we check whether the requested number of bytes has already been read. If that’s the case, we complete the result (which causes the client callback to be called). Otherwise we ask for more bytes in another BeginSocketReceive call.

  1. class SocketReceiveAsyncResult : AsyncResult
  2. {
  3.     SizedTcpBaseChannel channel;
  4.     int size;
  5.     int bytesReadTotal;
  6.     byte[] buffer;
  7.     bool throwOnEmpty;
  8.  
  9.     public SocketReceiveAsyncResult(int size, bool throwOnEmpty, SizedTcpBaseChannel channel, AsyncCallback callback, object state)
  10.         : base(callback, state)
  11.     {
  12.         this.size = size;
  13.         this.channel = channel;
  14.         this.throwOnEmpty = throwOnEmpty;
  15.         this.bytesReadTotal = 0;
  16.         this.buffer = channel.bufferManager.TakeBuffer(size);
  17.  
  18.         bool success = false;
  19.         try
  20.         {
  21.             IAsyncResult socketReceiveResult = channel.BeginSocketReceive(this.buffer, bytesReadTotal, size, OnReadBytes, this);
  22.             if (socketReceiveResult.CompletedSynchronously)
  23.             {
  24.                 if (CompleteReadBytes(socketReceiveResult))
  25.                 {
  26.                     base.Complete(true);
  27.                 }
  28.             }
  29.             success = true;
  30.         }
  31.         finally
  32.         {
  33.             if (!success)
  34.             {
  35.                 this.Cleanup();
  36.             }
  37.         }
  38.     }
  39.  
  40.     void Cleanup()
  41.     {
  42.         if (this.buffer != null)
  43.         {
  44.             channel.bufferManager.ReturnBuffer(this.buffer);
  45.             this.buffer = null;
  46.         }
  47.     }
  48.  
  49.     bool CompleteReadBytes(IAsyncResult result)
  50.     {
  51.         int bytesRead = channel.EndSocketReceive(result);
  52.         bytesReadTotal += bytesRead;
  53.         if (bytesRead == 0)
  54.         {
  55.             if (size == 0 || !throwOnEmpty)
  56.             {
  57.                 channel.bufferManager.ReturnBuffer(this.buffer);
  58.                 this.buffer = null;
  59.                 return true;
  60.             }
  61.             else
  62.             {
  63.                 throw new CommunicationException("Premature EOF reached");
  64.             }
  65.         }
  66.  
  67.         while (bytesReadTotal < size)
  68.         {
  69.             IAsyncResult socketReceiveResult = channel.BeginSocketReceive(buffer, bytesReadTotal, size - bytesReadTotal, OnReadBytes, this);
  70.             if (!socketReceiveResult.CompletedSynchronously)
  71.             {
  72.                 return false;
  73.             }
  74.         }
  75.  
  76.         return true;
  77.     }
  78.  
  79.     static void OnReadBytes(IAsyncResult result)
  80.     {
  81.         if (result.CompletedSynchronously)
  82.         {
  83.             return;
  84.         }
  85.  
  86.         SocketReceiveAsyncResult thisPtr = (SocketReceiveAsyncResult)result.AsyncState;
  87.  
  88.         Exception completionException = null;
  89.         bool completeSelf = false;
  90.         try
  91.         {
  92.             completeSelf = thisPtr.CompleteReadBytes(result);
  93.         }
  94.         catch (Exception e)
  95.         {
  96.             completeSelf = true;
  97.             completionException = e;
  98.             thisPtr.Cleanup();
  99.         }
  100.  
  101.         if (completeSelf)
  102.         {
  103.             thisPtr.Complete(false, completionException);
  104.         }
  105.     }
  106.  
  107.     public static byte[] End(IAsyncResult result)
  108.     {
  109.         try
  110.         {
  111.             SocketReceiveAsyncResult thisPtr = AsyncResult.End<SocketReceiveAsyncResult>(result);
  112.             return thisPtr.buffer;
  113.         }
  114.         catch (ObjectDisposedException)
  115.         {
  116.             return null;
  117.         }
  118.     }
  119. }

And now that the base operation is done, we can move onto the functions which call it. And that’s essentially the pattern for writing out asynchronous channels. Start from the bottom, and build up the stack until the function which we need (in this case [Begin/End]Request from the IRequestChannel interface). Next up, WriteData and ReadData. The first one, however, is fairly simple – it doesn’t need to iterate, doesn’t need to call multiple asynchronous requests, it simply needs to add the length to the buffer (a synchronous operation), and then send the bytes. So for this case, we don’t need to create a new AsyncResult implementation, simply chaining the call directly after modifying the input, as shown below.

  1. private IAsyncResult BeginWriteData(ArraySegment<byte> data, TimeSpan timeout,
  2.     AsyncCallback callback, object state)
  3. {
  4.     ArraySegment<byte> toSend = this.AddLengthToBuffer(data);
  5.     return new SocketSendAsyncResult(toSend, this, callback, state);
  6. }
  7.  
  8. void EndWriteData(IAsyncResult result)
  9. {
  10.     SocketSendAsyncResult.End(result);
  11. }

Reading data, however, goes back to the asynchronous chain. To read a properly framed request, we need to make two read calls: one for the 4-byte length, and one for the actual data (all as cascading asynchronous calls). So we do need a new AsyncResult class at this time.

  1. IAsyncResult BeginReadData(AsyncCallback callback, object state)
  2. {
  3.     return new ReadDataAsyncResult(this, callback, state);
  4. }
  5.  
  6. ArraySegment<byte> EndReadData(IAsyncResult result)
  7. {
  8.     return ReadDataAsyncResult.End(result);
  9. }

The ReadDataAsyncResult goes like the previous ones. The constructor initializes some variables, then makes the first asynchronous call (to receive the 4 length bytes). When that operation completes (on CompleteDrainLength), the object makes the second asynchronous call (to receive the actual bytes), and finally when that is done, the AsyncResult completes itself, saving the data read in an instance variable to be returned in the End call.

  1. class ReadDataAsyncResult : AsyncResult
  2. {
  3.     ArraySegment<byte> buffer;
  4.     SizedTcpBaseChannel channel;
  5.     int dataLength;
  6.     byte[] lengthBytes;
  7.     byte[] data;
  8.  
  9.     public ReadDataAsyncResult(SizedTcpBaseChannel channel, AsyncCallback callback, object state)
  10.         : base(callback, state)
  11.     {
  12.         this.channel = channel;
  13.  
  14.         bool success = false;
  15.         try
  16.         {
  17.             IAsyncResult drainLengthResult = channel.BeginSocketReceiveBytes(4, false, OnDrainLength, this);
  18.             if (drainLengthResult.CompletedSynchronously)
  19.             {
  20.                 if (CompleteDrainLength(drainLengthResult))
  21.                 {
  22.                     base.Complete(true);
  23.                 }
  24.             }
  25.  
  26.             success = true;
  27.         }
  28.         catch (CommunicationException e)
  29.         {
  30.             base.Complete(true, e);
  31.         }
  32.         finally
  33.         {
  34.             if (!success)
  35.             {
  36.                 this.Cleanup();
  37.             }
  38.         }
  39.     }
  40.  
  41.     bool CompleteDrainLength(IAsyncResult result)
  42.     {
  43.         this.lengthBytes = channel.EndSocketReceiveBytes(result);
  44.         if (this.lengthBytes == null)
  45.         {
  46.             this.buffer = new ArraySegment<byte>();
  47.             return true;
  48.         }
  49.  
  50.         this.dataLength = Formatting.BytesToSize(this.lengthBytes, 0);
  51.  
  52.         IAsyncResult readDataResult = channel.BeginSocketReceiveBytes(this.dataLength, OnReadData, this);
  53.         if (!readDataResult.CompletedSynchronously)
  54.         {
  55.             return false;
  56.         }
  57.  
  58.         return CompleteReadData(result);
  59.     }
  60.  
  61.     bool CompleteReadData(IAsyncResult result)
  62.     {
  63.         data = channel.EndSocketReceiveBytes(result);
  64.         this.buffer = new ArraySegment<byte>(this.data, 0, this.dataLength);
  65.         CleanupLength();
  66.         return true;
  67.     }
  68.  
  69.     static void OnDrainLength(IAsyncResult result)
  70.     {
  71.         if (result.CompletedSynchronously)
  72.         {
  73.             return;
  74.         }
  75.  
  76.         ReadDataAsyncResult thisPtr = (ReadDataAsyncResult)result.AsyncState;
  77.  
  78.         Exception completionException = null;
  79.         bool completeSelf = false;
  80.         try
  81.         {
  82.             completeSelf = thisPtr.CompleteDrainLength(result);
  83.         }
  84.         catch (Exception e)
  85.         {
  86.             completeSelf = true;
  87.             completionException = e;
  88.             thisPtr.Cleanup();
  89.         }
  90.  
  91.         if (completeSelf)
  92.         {
  93.             thisPtr.Complete(false, completionException);
  94.         }
  95.     }
  96.  
  97.     static void OnReadData(IAsyncResult result)
  98.     {
  99.         if (result.CompletedSynchronously)
  100.         {
  101.             return;
  102.         }
  103.  
  104.         ReadDataAsyncResult thisPtr = (ReadDataAsyncResult)result.AsyncState;
  105.  
  106.         Exception completionException = null;
  107.         bool completeSelf = false;
  108.         try
  109.         {
  110.             completeSelf = thisPtr.CompleteReadData(result);
  111.         }
  112.         catch (Exception e)
  113.         {
  114.             completeSelf = true;
  115.             completionException = e;
  116.             thisPtr.Cleanup();
  117.         }
  118.  
  119.         if (completeSelf)
  120.         {
  121.             thisPtr.Complete(false, completionException);
  122.         }
  123.     }
  124.  
  125.     public static ArraySegment<byte> End(IAsyncResult result)
  126.     {
  127.         ReadDataAsyncResult thisPtr = AsyncResult.End<ReadDataAsyncResult>(result);
  128.         return thisPtr.buffer;
  129.     }
  130.  
  131.     void Cleanup()
  132.     {
  133.         if (this.data != null)
  134.         {
  135.             this.channel.bufferManager.ReturnBuffer(data);
  136.             this.data = null;
  137.         }
  138.  
  139.         CleanupLength();
  140.     }
  141.  
  142.     void CleanupLength()
  143.     {
  144.         if (this.lengthBytes != null)
  145.         {
  146.             this.channel.bufferManager.ReturnBuffer(lengthBytes);
  147.             this.lengthBytes = null;
  148.         }
  149.     }
  150. }

So at this point we’re done with the “base” operations (those dealing with sockets only), and can start moving on to the channel operations themselves (those which deal with the Message type): SendMessage and ReceiveMessage. Those are quite similar: Send first encodes the Message into the data bytes, then writes the data to the wire; Receive first reads data from the wire, then decodes them into a Message. This is similar to the WriteData operation: a combination of one synchronous and one asynchronous operation. So thankfully we don’t need another AsyncResult class for those. As with the other operations, I also have unit tests for those, but I’ll skip them here (they’re in the code gallery download).

  1. public IAsyncResult BeginSendMessage(
  2.     Message message, TimeSpan timeout, AsyncCallback callback, object state)
  3. {
  4.     base.ThrowIfDisposedOrNotOpen();
  5.     ArraySegment<byte> encodedMessage = this.EncodeMessage(message);
  6.     return this.BeginWriteData(encodedMessage, timeout, callback, state);
  7. }
  8.  
  9. public void EndSendMessage(IAsyncResult result)
  10. {
  11.     this.EndWriteData(result);
  12. }
  13.  
  14. public IAsyncResult BeginReceiveMessage(
  15.     TimeSpan timeout, AsyncCallback callback, object state)
  16. {
  17.     base.ThrowIfDisposedOrNotOpen();
  18.     return this.BeginReadData(callback, state);
  19. }
  20.  
  21. public Message EndReceiveMessage(IAsyncResult result)
  22. {
  23.     ArraySegment<byte> encodedBytes = this.EndReadData(result);
  24.     return this.DecodeMessage(encodedBytes);
  25. }

And now all the basic operations are done. All we need now is to (finally!) implement the Begin/End request operations, in the request channel itself. A request operation is a simple pair of Send/Receive calls (i.e., two asynchronous calls), so we need, yes, a new AsyncResult class to handle this case. First the methods themselves, trivial:

  1. public IAsyncResult BeginRequest(
  2.     Message message, TimeSpan timeout, AsyncCallback callback, object state)
  3. {
  4.     return new RequestAsyncResult(message, timeout, this, callback, state);
  5. }
  6.  
  7. public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
  8. {
  9.     return this.BeginRequest(message, base.DefaultSendTimeout, callback, state);
  10. }
  11.  
  12. public Message EndRequest(IAsyncResult result)
  13. {
  14.     return RequestAsyncResult.End(result);
  15. }

And the RequestAsyncResult class. As usual, the constructor calls the first asynchronous operation (Send), and when it’s complete (synchronously or not), the completion of the operation calls the second operation (Receive).

  1. class RequestAsyncResult : AsyncResult
  2. {
  3.     private Message response;
  4.     private TimeSpan timeout;
  5.     private SizedTcpRequestChannel channel;
  6.  
  7.     public RequestAsyncResult(Message message, TimeSpan timeout, SizedTcpRequestChannel channel, AsyncCallback callback, object state)
  8.         : base(callback, state)
  9.     {
  10.         this.channel = channel;
  11.         this.timeout = timeout;
  12.  
  13.         IAsyncResult sendResult = channel.BeginSendMessage(message, timeout, OnSend, this);
  14.         if (sendResult.CompletedSynchronously)
  15.         {
  16.             this.CompleteSend(sendResult);
  17.         }
  18.     }
  19.  
  20.     static void OnSend(IAsyncResult asyncResult)
  21.     {
  22.         if (asyncResult.CompletedSynchronously)
  23.         {
  24.             return;
  25.         }
  26.  
  27.         RequestAsyncResult thisPtr = (RequestAsyncResult)asyncResult.AsyncState;
  28.         Exception completeException = null;
  29.         bool completeSelf = false;
  30.         try
  31.         {
  32.             completeSelf = thisPtr.CompleteSend(asyncResult);
  33.         }
  34.         catch (Exception e)
  35.         {
  36.             completeException = e;
  37.             completeSelf = true;
  38.         }
  39.  
  40.         if (completeSelf)
  41.         {
  42.             thisPtr.Complete(false, completeException);
  43.         }
  44.     }
  45.  
  46.     bool CompleteSend(IAsyncResult asyncResult)
  47.     {
  48.         this.channel.EndSendMessage(asyncResult);
  49.  
  50.         IAsyncResult receiveResult = this.channel.BeginReceiveMessage(this.timeout, OnReceive, this);
  51.         if (!receiveResult.CompletedSynchronously)
  52.         {
  53.             return false;
  54.         }
  55.  
  56.         this.CompleteReceive(asyncResult);
  57.         return false;
  58.     }
  59.  
  60.     static void OnReceive(IAsyncResult asyncResult)
  61.     {
  62.         if (asyncResult.CompletedSynchronously)
  63.         {
  64.             return;
  65.         }
  66.  
  67.         RequestAsyncResult thisPtr = (RequestAsyncResult)asyncResult.AsyncState;
  68.         Exception completeException = null;
  69.         try
  70.         {
  71.             thisPtr.CompleteReceive(asyncResult);
  72.         }
  73.         catch (Exception e)
  74.         {
  75.             completeException = e;
  76.         }
  77.  
  78.         thisPtr.Complete(false, completeException);
  79.     }
  80.  
  81.     void CompleteReceive(IAsyncResult asyncResult)
  82.     {
  83.         this.response = this.channel.EndReceiveMessage(asyncResult);
  84.     }
  85.  
  86.     internal static Message End(IAsyncResult result)
  87.     {
  88.         RequestAsyncResult thisPtr = AsyncResult.End<RequestAsyncResult>(result);
  89.         return thisPtr.response;
  90.     }
  91. }

And finally the request asynchronous operation is complete. For this case, it’s actually interesting to show the unit test. Although the class is internal (so we need to use the reflection helper to instantiate it), we know that the type inherits from ChannelBase and implements IRequestChannel, so we can use those public types to write the tests in a cleaner way.

  1. [Fact]
  2. public void AsynchronousRequest()
  3. {
  4.     byte[] data = new byte[36];
  5.     Random rndGen = new Random();
  6.     rndGen.NextBytes(data);
  7.  
  8.     Message input = Formatting.BytesToMessage(data);
  9.  
  10.     ManualResetEvent evt = new ManualResetEvent(false);
  11.     Uri serverUri = new Uri(SizedTcpTransportBindingElement.SizedTcpScheme + "://" + Environment.MachineName + ":" + Port);
  12.     object channel = ReflectionHelper.CreateInstance(
  13.         typeof(SizedTcpTransportBindingElement),
  14.         "JsonRpcOverTcp.Channels.SizedTcpRequestChannel",
  15.         new ByteStreamMessageEncodingBindingElement().CreateMessageEncoderFactory().Encoder,
  16.         BufferManager.CreateBufferManager(int.MaxValue, int.MaxValue),
  17.         Mocks.GetChannelManagerBase(),
  18.         new EndpointAddress(serverUri),
  19.         serverUri);
  20.  
  21.     ChannelBase channelBase = (ChannelBase)channel;
  22.     IRequestChannel requestChannel = (IRequestChannel)channel;
  23.     channelBase.Open();
  24.  
  25.     object state = new object();
  26.     bool success = true;
  27.     requestChannel.BeginRequest(input, new AsyncCallback(delegate(IAsyncResult asyncResult)
  28.     {
  29.         try
  30.         {
  31.             if (!Object.ReferenceEquals(asyncResult.AsyncState, state))
  32.             {
  33.                 success = false;
  34.                 Console.WriteLine("Error, state not preserved");
  35.             }
  36.             else
  37.             {
  38.                 Message output = requestChannel.EndRequest(asyncResult);
  39.  
  40.                 try
  41.                 {
  42.                     byte[] outputBytes = Formatting.MessageToBytes(output);
  43.                     Assert.Equal(data, outputBytes, new ArrayComparer<byte>());
  44.                 }
  45.                 catch (Exception e)
  46.                 {
  47.                     Console.WriteLine("Error: " + e);
  48.                     success = false;
  49.                 }
  50.             }
  51.         }
  52.         finally
  53.         {
  54.             evt.Set();
  55.         }
  56.     }), state);
  57.  
  58.     evt.WaitOne();
  59.     Assert.True(success, "Error in callback");
  60.     channelBase.Close();
  61. }

So are we done? Not quite. Notice that opening the channel actually involves some networking calls (first get the IP address of the server from the name, a DNS call, then connect the socket to the server, with the TCP handshaking). So we need to override the asynchronous open calls (BeginOpen and EndOpen) to make sure that we don’t block the calling thread. And since we’re dealing with at least two asynchronous calls… you know the drill by now.

  1. protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
  2. {
  3.     return new ConnectAsyncResult(timeout, this, callback, state);
  4. }
  5.  
  6. protected override void OnEndOpen(IAsyncResult result)
  7. {
  8.     ConnectAsyncResult.End(result);
  9. }

And the ConnectAsyncResult class. Notice that a machine may have multiple IP addresses, so we need to account for that when trying to connect the socket.

  1. class ConnectAsyncResult : AsyncResult
  2. {
  3.     TimeSpan timeout;
  4.     SizedTcpRequestChannel channel;
  5.     IPHostEntry hostEntry;
  6.     Socket socket;
  7.     bool connected;
  8.     int currentEntry;
  9.     int port;
  10.  
  11.     public ConnectAsyncResult(TimeSpan timeout, SizedTcpRequestChannel channel, AsyncCallback callback, object state)
  12.         : base(callback, state)
  13.     {
  14.         // production code should use this timeout
  15.         this.timeout = timeout;
  16.         this.channel = channel;
  17.  
  18.         IAsyncResult dnsGetHostResult = Dns.BeginGetHostEntry(channel.Via.Host, OnDnsGetHost, this);
  19.         if (!dnsGetHostResult.CompletedSynchronously)
  20.         {
  21.             return;
  22.         }
  23.  
  24.         if (this.CompleteDnsGetHost(dnsGetHostResult))
  25.         {
  26.             base.Complete(true);
  27.         }
  28.     }
  29.  
  30.     static void OnDnsGetHost(IAsyncResult result)
  31.     {
  32.         if (result.CompletedSynchronously)
  33.         {
  34.             return;
  35.         }
  36.  
  37.         ConnectAsyncResult thisPtr = (ConnectAsyncResult)result.AsyncState;
  38.  
  39.         Exception completionException = null;
  40.         bool completeSelf = false;
  41.         try
  42.         {
  43.             completeSelf = thisPtr.CompleteDnsGetHost(result);
  44.         }
  45.         catch (Exception e)
  46.         {
  47.             completeSelf = true;
  48.             completionException = e;
  49.         }
  50.  
  51.         if (completeSelf)
  52.         {
  53.             thisPtr.Complete(false, completionException);
  54.         }
  55.     }
  56.  
  57.     bool CompleteDnsGetHost(IAsyncResult result)
  58.     {
  59.         try
  60.         {
  61.             this.hostEntry = Dns.EndGetHostEntry(result);
  62.         }
  63.         catch (SocketException socketException)
  64.         {
  65.             throw new EndpointNotFoundException("Unable to resolve host" + channel.Via.Host, socketException);
  66.         }
  67.  
  68.         port = this.channel.Via.Port;
  69.         if (port == -1)
  70.         {
  71.             port = 8000; // Let's call it the default port for our protocol
  72.         }
  73.  
  74.         IAsyncResult socketConnectResult = this.BeginSocketConnect();
  75.         if (!socketConnectResult.CompletedSynchronously)
  76.         {
  77.             return false;
  78.         }
  79.  
  80.         return this.CompleteSocketConnect(socketConnectResult);
  81.     }
  82.  
  83.     IAsyncResult BeginSocketConnect()
  84.     {
  85.         IPAddress address = this.hostEntry.AddressList[this.currentEntry];
  86.         this.socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  87.         while (true)
  88.         {
  89.             try
  90.             {
  91.                 return this.socket.BeginConnect(new IPEndPoint(address, this.port), OnSocketConnect, this);
  92.             }
  93.             catch (SocketException socketException)
  94.             {
  95.                 if (this.currentEntry == this.hostEntry.AddressList.Length - 1)
  96.                 {
  97.                     throw ConvertSocketException(socketException, "Connect");
  98.                 }
  99.  
  100.                 this.currentEntry++;
  101.             }
  102.         }
  103.     }
  104.  
  105.     static void OnSocketConnect(IAsyncResult result)
  106.     {
  107.         if (result.CompletedSynchronously)
  108.         {
  109.             return;
  110.         }
  111.  
  112.         ConnectAsyncResult thisPtr = (ConnectAsyncResult)result.AsyncState;
  113.  
  114.         Exception completionException = null;
  115.         bool completeSelf = false;
  116.         try
  117.         {
  118.             completeSelf = thisPtr.CompleteSocketConnect(result);
  119.         }
  120.         catch (Exception e)
  121.         {
  122.             completeSelf = true;
  123.             completionException = e;
  124.         }
  125.  
  126.         if (completeSelf)
  127.         {
  128.             thisPtr.Complete(false, completionException);
  129.         }
  130.     }
  131.  
  132.     bool CompleteSocketConnect(IAsyncResult result)
  133.     {
  134.         while (!this.connected && this.currentEntry < this.hostEntry.AddressList.Length)
  135.         {
  136.             try
  137.             {
  138.                 socket.EndConnect(result);
  139.                 connected = true;
  140.                 break;
  141.             }
  142.             catch (SocketException socketException)
  143.             {
  144.                 if (this.currentEntry == this.hostEntry.AddressList.Length - 1)
  145.                 {
  146.                     throw ConvertSocketException(socketException, "Connect");
  147.                 }
  148.  
  149.                 this.currentEntry++;
  150.             }
  151.  
  152.             result = BeginSocketConnect();
  153.             if (!result.CompletedSynchronously)
  154.             {
  155.                 return false;
  156.             }
  157.         }
  158.  
  159.         this.channel.InitializeSocket(socket);
  160.         return true;
  161.     }
  162.  
  163.     public static void End(IAsyncResult result)
  164.     {
  165.         AsyncResult.End<ConnectAsyncResult>(result);
  166.     }
  167. }

Now, we’re finally done! Now for the client application to test it. An asynchronous interface (which can be typed if the endpoint is using the behavior we implemented in the previous post) gives us the starting point for testing it.

  1. [ServiceContract]
  2. public interface ITypedTestAsync
  3. {
  4.     [OperationContract(AsyncPattern = true)]
  5.     IAsyncResult BeginAdd(int x, int y, AsyncCallback callback, object state);
  6.     int EndAdd(IAsyncResult asyncResult);
  7.     [OperationContract(AsyncPattern = true)]
  8.     IAsyncResult BeginSubtract(int x, int y, AsyncCallback callback, object state);
  9.     int EndSubtract(IAsyncResult asyncResult);
  10.     [OperationContract(AsyncPattern = true)]
  11.     IAsyncResult BeginMultiply(int x, int y, AsyncCallback callback, object state);
  12.     int EndMultiply(IAsyncResult asyncResult);
  13.     [OperationContract(AsyncPattern = true)]
  14.     IAsyncResult BeginDivide(int x, int y, AsyncCallback callback, object state);
  15.     int EndDivide(IAsyncResult asyncResult);
  16. }

And using that interface in a program to verify that it worked (which, in my case, it did, since I had fixed the errors while creating the unit tests).

  1. Console.WriteLine("Now using the typed asynchronous interface");
  2. var asyncTypedFactory = new ChannelFactory<ITypedTestAsync>(binding, address);
  3. asyncTypedFactory.Endpoint.Behaviors.Add(new JsonRpcEndpointBehavior());
  4. ITypedTestAsync asyncTypedProxy = asyncTypedFactory.CreateChannel();
  5.  
  6. AutoResetEvent evt = new AutoResetEvent(false);
  7. Console.WriteLine("Calling BeginAdd");
  8. asyncTypedProxy.BeginAdd(5, 8, delegate(IAsyncResult ar)
  9. {
  10.     result = asyncTypedProxy.EndAdd(ar);
  11.     Console.WriteLine(" ==> Result: {0}", result);
  12.     Console.WriteLine();
  13.     evt.Set();
  14. }, null);
  15. evt.WaitOne();
  16.  
  17. Console.WriteLine("Calling BeginMultiply");
  18. asyncTypedProxy.BeginMultiply(5, 8, delegate(IAsyncResult ar)
  19. {
  20.     result = asyncTypedProxy.EndMultiply(ar);
  21.     Console.WriteLine(" ==> Result: {0}", result);
  22.     Console.WriteLine();
  23.     evt.Set();
  24. }, null);
  25. evt.WaitOne();
  26.  
  27. Console.WriteLine("Calling BeginDivide (throws)");
  28. asyncTypedProxy.BeginDivide(5, 0, delegate(IAsyncResult ar)
  29. {
  30.     try
  31.     {
  32.         result = asyncTypedProxy.EndDivide(ar);
  33.         Console.WriteLine(" ==> Result: {0}", result);
  34.     }
  35.     catch (JsonRpcException e)
  36.     {
  37.         Console.WriteLine("Error: {0}", e.JsonException);
  38.     }
  39.  
  40.     Console.WriteLine();
  41.     evt.Set();
  42. }, null);
  43. evt.WaitOne();

And now, we’re really, really done. Here’s a recap of this “mini-series”:

Coming up

Next up, a different kind of channel – let’s move to the server side with an IReplyChannel.

[Code in this post]

[Back to the index]