/*
namespace UdpRendezvous
class ServicePublisher
class ServiceLocator
by: Shawn A. Van Ness (http://www.arithex.com/)
rev: 2003.04.08
This pair of C# classes wraps UdpClient's multicast functionality, for use
as a simple client-server rendezvous mechanism.
See Client.cs and Server.cs, in the test subdirectory, for sample usage.
*/
using System;
using System.Net;
using System.Net.Sockets;
namespace UdpRendezvous
{
//
// Public types
///
/// Advertises a service on the network via UDP multicast groups.
///
public sealed class ServicePublisher
{
//
// Construction
///
/// Initializes a new instance of the ServicePublisher class, for the specified service id.
///
/// Unique identifer for the client/server protocol being advertised.
public ServicePublisher(Guid serviceId)
{
this.serviceId = serviceId;
this.listener = null;
this.response = null;
}
//
// Public API
///
/// Begin advertising the presence of the service.
///
/// Property-bag of protocol-specific connection parameters.
public void PublishServiceEndpoint(System.Collections.IDictionary endpointProps)
{
PublishServiceEndpoint(endpointProps,UdpMulticastGroupSettings.DefaultGroupTTL);
}
///
/// Begin advertising the presence of the service.
///
/// Property-bag of protocol-specific connection parameters.
/// The number of router-hops to advertise across.
public void PublishServiceEndpoint(System.Collections.IDictionary endpointProps, int ttl)
{
// Prepare canned response: serialize serviceId and endpointProps into byte[]
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
System.IO.MemoryStream ms = new System.IO.MemoryStream();
ms.Write(this.serviceId.ToByteArray(), 0, 16);
bf.Serialize(ms,endpointProps);
this.response = ms.ToArray();
// Initalize a new UDP socket for the multicast group
this.listener = new UdpClient(UdpMulticastGroupSettings.ServerPort);
// Join the multicast group (sends an IGMP group-membership report to routers)
this.listener.JoinMulticastGroup(UdpMulticastGroupSettings.GroupAddress, ttl);
// Punt remainder of implementation to background thread (UdpClient.Receive blocks!)
System.Threading.Thread listenerThread =
new System.Threading.Thread(new System.Threading.ThreadStart(this.ThreadProc));
listenerThread.Start();
}
///
/// Stops advertising the service.
///
public void StopThePresses()
{
if (this.listener != null)
{
// Cleanly withdraw our membership from the multicast group
this.listener.DropMulticastGroup(UdpMulticastGroupSettings.GroupAddress);
// Closing the underlying socket will cause UdpClient.Receive to throw
this.listener.Close();
this.listener = null;
}
}
//
// Implementation
private void ThreadProc()
{
// Loop forever (until underlying socket is closed, anyway)
try
{
while (true)
{
try
{
// Wait for broadcast... will block until data recv'd, or underlying socket is closed
IPEndPoint callerEndpoint = null;
byte[] request = this.listener.Receive( ref callerEndpoint);
// Verify first 128 bits are indeed our guid
if (request.Length >= 16)
{
byte[] temp = new byte[16];
request.CopyTo(temp,0);
Guid requestGuid = new Guid(temp);
if (requestGuid == this.serviceId)
{
// Send response (our guid, followed by serialized endpoint info)
this.listener.Send(this.response, this.response.Length, callerEndpoint);
}
}
}
catch (System.Net.Sockets.SocketException)
{ } // expected (client got too impatient?)
}
}
catch (System.ObjectDisposedException)
{ } // expected
catch (System.NullReferenceException)
{ } // also expected?
}
private readonly Guid serviceId;
private UdpClient listener;
private byte[] response;
}
///
/// Searches hosts on the network for a specific service, via UDP multicast groups.
///
public sealed class ServiceLocator
{
//
// Construction
private ServiceLocator()
{ } // this class is noncreatable (static members only)
//
// Public API
///
/// Locates hosts on the network which expose the requested service.
///
/// Unique identifier for the requested service.
/// An array of HostResponse structures.
public static HostResponse[] LocateService(Guid serviceId)
{
return LocateService(serviceId, UdpMulticastGroupSettings.DefaultClientTimeout);
}
///
/// Locates hosts on the network which expose the requested service.
///
/// Unique identifier for the requested service.
/// Time to wait for responses from remote hosts.
/// An array of HostResponse structures.
public static HostResponse[] LocateService(Guid serviceId, System.TimeSpan timeout)
{
return LocateService(serviceId, timeout.Milliseconds);
}
///
/// Locates hosts on the network which expose the requested service.
///
/// Unique identifier for the requested service.
/// Time (in milliseconds) to wait for responses from remote hosts.
/// An array of HostResponse structures.
public static HostResponse[] LocateService(Guid serviceId, int millisecondTimeout)
{
// Impose reasonable range on timeout
if (millisecondTimeout < 100)
millisecondTimeout = 100;
else if (millisecondTimeout > 7000)
millisecondTimeout = 7000;
// Dynamically allocate client port
UdpClient sender = new UdpClient();
// Construct simple datagram w/ serviceId
byte[] request = serviceId.ToByteArray();
IPEndPoint groupEP = new IPEndPoint(UdpMulticastGroupSettings.GroupAddress,UdpMulticastGroupSettings.ServerPort);
// Send the query
sender.Send(request, request.Length, groupEP);
// Accumulate responses on a threadpool thread
ResponseAccumProc rap = new ResponseAccumProc(ResponseAccumProcImpl);
IAsyncResult ar = rap.BeginInvoke(sender,serviceId,null,null);
// Wait the requisite amount of time, then shut the door
System.Threading.Thread.Sleep(millisecondTimeout);
sender.Close(); // will kick the bkgrnd thread out of the blocked recv method
// Return the results
HostResponse[] hrs = rap.EndInvoke(ar); // waits for async delegate to complete
return hrs;
}
///
/// This nested-type encapsulates the IPAddress and other response info, from a remote service endpoint.
///
public struct HostResponse
{
private readonly IPAddress address;
private readonly System.Collections.IDictionary endpointProps;
internal HostResponse(IPAddress address, System.Collections.IDictionary endpointProps)
{
this.address = address;
this.endpointProps = endpointProps;
}
///
/// Gets the IPAddress of the responding host.
///
public IPAddress IPAddress
{
get
{ return this.address; }
}
///
/// Gets a collection of protocol-specific endpoint info from the responding host.
///
public System.Collections.IDictionary EndpointProperties
{
get
{ return this.endpointProps; }
}
// Canonical value-type comparison goo (uniqueness based on address)
///
/// Overridden. Returns a value indicating whether this instance is equal to a specified object.
///
/// An object to compare with this instance.
/// true if obj is an instance of and equals the value of this instance; otherwise, false.
public override bool Equals(object obj)
{
return ((obj is HostResponse) &&
(this.address.Address == ((HostResponse)obj).address.Address));
}
///
/// Overridden. Returns the hash code for this instance.
///
/// A 32-bit signed integer hash code.
public override int GetHashCode()
{
return this.address.Address.GetHashCode();
}
///
/// Compares two HostResponse structures, based on the originating IPAddress.
///
/// A HostResponse structure.
/// A HostResponse structure.
/// true if the two responses are from the same IP address, false otherwise.
public static bool operator==( HostResponse a, HostResponse b)
{
return (a.address.Address==b.address.Address);
}
///
/// Compares two HostResponse structures, based on the originating IPAddress.
///
/// A HostResponse structure.
/// A HostResponse structure.
/// false if the two responses are from the same IP address, true otherwise.
public static bool operator !=( HostResponse a, HostResponse b)
{
return !(a==b);
}
}
//
// Implementation
internal delegate HostResponse[] ResponseAccumProc(UdpClient udpClient, Guid serviceId);
internal static HostResponse[] ResponseAccumProcImpl(UdpClient udpClient, Guid serviceId)
{
// Accumulate responses
System.Collections.ArrayList responses = new System.Collections.ArrayList();
// Loop forever (until underlying socket is closed, anyway)
try
{
while (true)
{
// Grab a response datagram
IPEndPoint remoteEndpoint = null;
byte[] response = udpClient.Receive( ref remoteEndpoint); //blocks until socket closed
// Unmarshal the response
System.IO.MemoryStream responseStream = new System.IO.MemoryStream(response,false);
// Format is a 16-byte guid, followed by serialized propertybag
if (response.Length >= 16)
{
byte[] temp = new byte[16];
responseStream.Read(temp,0,16);
Guid guid = new Guid(temp);
if (guid == serviceId)
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
object endpointProps = bf.Deserialize(responseStream);
responses.Add(new HostResponse(remoteEndpoint.Address,(System.Collections.IDictionary)endpointProps));
}
}
}
}
catch (System.ObjectDisposedException)
{ } // expected
// catch (System.Net.Sockets.SocketException)
// { } // expected?
return responses.ToArray(typeof(HostResponse)) as HostResponse[];
}
}
//
// Helper classes
// Obtains addr/port from .config file, or else fallback to default values
internal sealed class UdpMulticastGroupSettings
{
private UdpMulticastGroupSettings()
{ } // this class is noncreatable (static members only)
public static IPAddress GroupAddress
{
get
{
try
{ return IPAddress.Parse(System.Configuration.ConfigurationSettings.AppSettings["GroupAddress"]); }
catch
{ return IPAddress.Parse("226.254.82.220"); }
}
}
public static int ServerPort
{
get
{
try
{ return Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["ServerPort"]); }
catch
{ return 11000; }
}
}
public static int DefaultGroupTTL
{
get
{
try
{ return Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["DefaultGroupTTL"]); }
catch
{ return 3; }
}
}
public static int DefaultClientTimeout
{
get
{
try
{ return Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["DefaultClientTimeout"]); }
catch
{ return 1000; }
}
}
}
}