Discovery Service - Part 3 - Coding Sanity
Friday, June 06, 2008 12:31 PM codingsanity

Discovery Service - Part 3

Right, so in Part 1 we looked at the requirements for a Discovery Service and in Part 2 we looked at how we'd host the WCF service. Now it's time to look at the UDP itself. First off though, if you look in Part 2, at the declaration I've got for DiscoveryServer, you'll see I'm inheriting from a DiscoveryBase. This base class will handle most of the UDP stuff, since both DiscoveryClient and DiscoveryServer need a very similar set of operations.

Let's look at the most important method on DiscoveryBase:

private UdpClient _udpClient;
private long _connected;
private int _timeToLive = 2;
private IPAddress _address;
internal void Prepare(IPAddress address, int localPort)
{
    // Ensure we're not already discovering
    if (Interlocked.CompareExchange(ref _connected, 1, 0) == 1)
        throw new InvalidOperationException("Already in discovery mode.");

    // Create the client
    _udpClient = new UdpClient(localPort);

    // If it's broadcast we set the broadcast flag
    if (IsBroadcast(address))
        _udpClient.EnableBroadcast = true;

    // If it's multicast we join the multicast group
    if (IsMulticast(address))
        _udpClient.JoinMulticastGroup(address, _timeToLive);

    // Store the location to connect to
    _address = address;
}

As you can see, it's not terribly complicated. There's a little bit of concurrency logic to ensure we're not already discovering, we create a UdpClient object, and then either set it to broadcast or join a multicast group depending on the IP address. Finally we cache the address so that our child classes can access it via the Address property. _timeToLive is an interesting property, it defines how many router hops any multicast broadcasts will go. For now, I've just set this to 2, but you could easily make it configurable.

Now let's hop back to DiscoveryServer and have a look at how it kicks off it's discovery:

public void Publish(IPAddress address, int port)
{
    Prepare(address, port);

    // Set up the discovery service
    _host = new ServiceHost<IDiscoveryService>(this, _address);
    Binding binding = BindingAddressParser.CreateTransportBinding(_address);
    _host.AddServiceEndpoint(typeof(IDiscoveryService), binding, _address);
    _host.Open();

    // Now asynchronously listen for any incoming requests
    Client.BeginReceive(Receive, null);
}

So, as you can see, it calls Prepare on DiscoveryBase, in order to ready itself for UDP operations. It then begins hosting the IDiscoveryService on a transport and binding determined by an address which is passed into the DiscoveryServers constructor. Finally it kicks off an asynchronous receive operation on the UDP client. Let's have a closer look at Receive:

private void Receive(IAsyncResult ar)
{
    if (Connected) // This will be false if the UDP client has been closed
    {
        IPEndPoint remoteEP = null;
        byte[] result = Client.EndReceive(ar, ref remoteEP);

        // Now asynchronously listen for any incoming requests
        Client.BeginReceive(Receive, null);

        // Is there actual information to fetch?
        if ((result != null) && (remoteEP != null) && (result.Length > 0))
        {
            string message = Encoding.ASCII.GetString(result);

            if (message == @"get\services")
            {
                // Handle the get services request by sending the address where the service information is hosted
                message = BindingAddressParser.ResolveAddressHostName(_address);
                byte[] response = Encoding.ASCII.GetBytes(message);
                Client.Send(response, response.Length, remoteEP);
            }
        }
    }
}

So, what happens is we check if we're connected (the Connected property on DiscoveryBase returns whether the _connected field we saw in Prepare is equal to 1), we then kick off another asynchronous receive just in case there's more requests on the way. Then we decide whether it's a valid request. If it it, we send the address where we hosted the IDiscoveryService.

In Part 4 we'll start looking at the DiscoveryClient.

Filed under: ,

Comments

No Comments