/* 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; } } } } }