Duplex communication and callbacks in WCF

I was reading in the book Enterprise Integration Patterns that my dev manager bought for the team,
on the Message Endpoint pattern on the way home from work a few days ago.

“A Message Endpoint can be used to send messages or receive them, but one instance does not do both”
From my experience, this wasn’t necessarily true.
So I decided to confirm my suspicion.
The following is a mix of what I read and tested out.

Callback Operations

Not all bindings support callback operations. Only bidirectional-capable bindings (TCP, IPC) support callback operations.
For example, because of its connectionless nature, HTTP cannot be used for callbacks,
and therefore you cannot use callbacks over the BasicHttpBinding or the WSHttpBinding.
The MSMQ bindings also don’t support duplex communication.

To support callbacks over HTTP, WCF offers the WSDualHttpBinding, which actually sets up two WS channels:
one for the calls from the client to the service and one for the calls from the service to the client.

Two way communication can be set up for MSMQ bindings by using two one way contracts.

The CompositeDuplexBindingElement is commonly used with transports, such as HTTP, that do not allow duplex communications natively.
TCP, IPC by contrast, does allow duplex communications natively and so does not require the use of this binding element
for the service to send messages back to a client.

Callbacks, Ports, and Channels

When you use either the NetTcpBinding, NetPeerTcpBinding or the NetNamedPipebinding, the callbacks enter the client on the outgoing channel the binding maintains to the service.
There is no need to open a new port or a pipe for the callbacks.
When you use the WSDualHttpBinding, WCF maintains a separate, dedicated HTTP channel for the callbacks,
because HTTP itself is a unidirectional protocol.
WCF auto-generates a ClientBaseAddress if one is not explicitly set by the user.
WCF selects port 80 by default for that callback channel,
and it passes the service a callback address that uses HTTP, the client machine name, and port 80.
While using port 80 makes sense for Internet-based services, it is of little value to intranet-based services.
In addition, if the client machine happens to also have IIS 5 or 6 running, port 80 will already be reserved,
and the client will not be able to host the callback endpoint.
(IIS7, by default, will allow sharing the port.)

Assigning a callback address

Fortunately, the WSDualHttpBinding offers the ClientBaseAddress property,
which you can use to configure a different callback address on the client.

public class WSDualHttpBinding : Binding,...
{
   public Uri ClientBaseAddress
   {get;set;}
   //More members
}

and configuring the clients base address in the clients config file.

<system.serviceModel>
   <client>
      <endpoint
         address  = "http://localhost:8008/MyService"
         binding  = "wsDualHttpBinding"
         bindingConfiguration = "ClientCallback"
         contract = "IMyContract"
      />
   </client>
   <bindings>
      <wsDualHttpBinding>
         <binding name = "ClientCallback"
            clientBaseAddress = "http://localhost:8009/"
         />
      </wsDualHttpBinding>
   </bindings>
</system.serviceModel>

MSDN CompositeDuplexBindingElement states
The client must expose an address at which the service can contact it to establish a connection from the service to the client.
This client address is provided by the ClientBaseAddress property.

WSDualHttpBinding.ClientBaseAddress uses the CompositeDuplexBindingElement’s ClientBaseAddress property.

x

x

As Juval Lowey states
The problem with using a config file to set the callback base address is that it precludes running multiple instances of the client on the same machine, which is something you are likely to do in any decent testing scenario. However, since the callback port need not be known to the service in advance, in actuality any available port will do. It is therefore better to set the client base address programmatically to any available port.
This can be automated by using one of the SetClientBaseAddress extension methods on the WsDualProxyHelper class.

public static class WsDualProxyHelper
{
   public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy,
      int port) where T : class
   {
      WSDualHttpBinding binding = proxy.Endpoint.Binding as WSDualHttpBinding;
      Debug.Assert(binding != null);
      binding.ClientBaseAddress = new Uri("http://localhost:"+ port + "/");
   }
   public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy)
      where T : class
   {
      lock(typeof(WsDualProxyHelper))
      {
         int portNumber = FindPort( );
         SetClientBaseAddress(proxy,portNumber);
         proxy.Open( );
      }
   }
   internal static int FindPort( )
   {
      IPEndPoint endPoint = new IPEndPoint(IPAddress.Any,0);
      using(Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
         socket.Bind(endPoint);
         IPEndPoint local = (IPEndPoint)socket.LocalEndPoint;
         return local.Port;
      }
   }
}

There’s also a good description of how to implement callbacks in WCF in the MSDN Duplex Services item.

I’ve also implemented something similar in a project I worked on last year.
It used a singleton service, and has about 30 clients hitting it at any one point in time.

About these ads

Tags: ,

4 Responses to “Duplex communication and callbacks in WCF”

  1. Alex Says:

    Why are you not just customizing the URL and let HTTP.sys take care of the port sharing? Especially as the URL only needs to be unique across different processes.

    Like:

    WSDualHttpBinding binding1 = new WSDualHttpBinding
    {
    ClientBaseAddress = new Uri(string.Format(“http://localhost:8000/DuplexTestUsingCode/Client/P{0}”, Process.GetCurrentProcess().Id))
    };

    var client = new ServiceReference1.CalculatorDuplexClient(new InstanceContext(new CalcCallback(“First: “)), binding1, new EndpointAddress(“http://localhost:8731/Design_Time_Addresses/DualClient/Service1/”));
    client.Open();

  2. binarymist Says:

    Hi Alex.

    That’s a good idea if you can guarantee the ability to share ports,
    but… if your client side is constrained to using Windows XP 32bit or lower, your fresh out of luck in regards to sharing ports.
    Windows XP 32bit only supports IIS 5.1.
    IIS 5.1 does not have HTTP.sys.

  3. mooieglazen Says:

    Is it true that in order to set the ClientBaseAddress the client needs to run as administrator (on windows 7)? Is there a way to run the client without administrator rights?

  4. binarymist Says:

    It was at least 3 years ago, but from memory, I don’t think we had to run the clients with elevated permissions.
    The client we were running was installed as a system tray app.
    that notified all the developers when a CI build was running.
    I’m pretty sure it just ran as system.
    Can you give any more detail?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

Join 210 other followers

%d bloggers like this: