Archive for May, 2010

Duplex communication and callbacks in WCF

May 23, 2010

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.

Advertisements

Setting up a NFS share in FreeNAS

May 16, 2010

This setup is quite different to how you would normally setup NFS on a *nix server.
I only use NFS in read only mode due to security concerns with NFS.
There are very few options you can configure and there is no point in modifying the /etc/rc.conf /etc/exports and there is no point in adding /etc/hosts.deny, /etc/hosts.allow

as they will be removed on server reboot. Hopefully these options will be added in the future or at least a work around made available.
Ideally I’d like to add the

-mapall=myuser:myusergroup

option to the /etc/exports but there is no point as it’s not persisted to hard disk.

In the Web UI under Services|NFS leave Number of servers as default of 4 and check the enable box. This options will allow 4 concurrent users to be logged into the share.

In the Web UI under Services|NFS|Shares add a share with Path of /mnt/FileServer/myNFSshare Network 192.168.0.0/24

Have to set Map all users to root to Yes. This is the same as including the no_root_squash option that can be put in the /etc/exports on a *nix box, but normally I’d choose root_squash, but this doesn’t work well for mounting at boot without the

-mapall=myuser:myusergroup

option in the /etc/exports
Setup my authorised network, All dirs and Read only to yes.

Added the following lines to /etc/rc.conf in FreeNAS as per this link

rpcbind_enable="YES"
nfs_server_enable="YES"
mountd_flags="-r"

Didn’t need the below line adding to the client machines /etc/rc.conf, although this said I did.

nfs_client_enable="YES"

After I restarted the server, the

mountd_flags="-r"

line was removed and the /mnt/.ssh dir was removed.
I no longer had key pair auth for SSH.
So had to go through the process of setting up that again.
The problem was any changes to /etc are not persisted to disk, so after a reboot it’s reset as it’s the FreeNAS ROM.
Matt Rude helped out with this

What I did was copy the /etc/rc.conf to my ~ which is /mnt/FileServer/home/myuser
Add the options again in /mnt/FileServer/home/myuser/rc.conf
Only the last option was actually not present and needed to be added.
Create a link from /etc/rc.conf to /mnt/FileServer/home/myuser/rc.conf

ln -s /mnt/FileServer/home/myuser/rc.conf /etc/rc.conf

Renamed the /etc/exports on the file server
Check the exports man page for the options…
Created an exports in /mnt/FileServer/home/myuser/ and added the following lines:

/mnt/FileServer/media -alldirs,ro -mapall=myuser:family -network 192.168.0.0 -mask 255.255.255.0
/mnt/FileServer/media -alldirs,ro -mapall=otheruser:family -network 192.168.0.0 -mask 255.255.255.0

Link the /etc/exports to /mnt/FileServer/home/myuser/exports

ln -s /mnt/FileServer/home/myuser/exports /etc/exports

None of the above links worked as they are removed on server reboot.
So basically the only options you have are on the Services|NFS web UI.

From here I created the /mnt/myfileserver/media directory on my client machines and set the myfileserver and media dir and perms to
/mnt/myfileserver was drwxrw—- myuser myusergroup
/mnt/myfileserver/media was drwxr-x— myuser users

Tried to mount the exported nfs share:

# mount myfreenasservername:/mnt/FileServer/media /mnt/myfileserver/media

This worked. So unmounted it.

# umount /mnt/myfileserver/media

Updated the /etc/fstab on the client machines so myfreenasservername:/mnt/FileServer/media would be mounted to /mnt/myfileserver/media on the client machines at boot.
add this to your client machines /etc/fstab

myfileservername:/mnt/FileServer/media /mnt/myfileserver/media nfs ro,hard,intr 0 0