This article details the use of a method known as “Auto Discovery”. The working example will mimic DHCP behavior by exchanging broadcast packets using
C#, Sockets and Threads.
Complete source-code download link:
http://eggheadcafe.com/FileUpload/-895676619_UDPAutoDiscovery1.0.zip
This article brings to C# Developers the possibility of taking advantage of the source-code to enable any .NET application to have a “Zero
Configuration” feature.
For this article I wrote two demo applications and a couple of classes to support
the proof-of-concept.
The code is clean and may serve as base code for any auto discovery service where
client-server applications may communicate between them without knowing their
exact IP address prior to first network hand-shake.
The classes and demo code are free for non-commercial use and any new or existing
client-server may take advantage of this technique. The only limitation is that
will work for servers and workstations that share the same “Netmask” so corporate
networks are the best environments for usage. Even thru VPN with correct routing
tables will work fine.
Some background for UDP Broadcasting:
When you connect your computer to a WiFi hotspot your computer will be asking for
an IP Address that must be provided by a DHCP server which is usually the same router you just connected. This works by broadcasting
packets over the network so it is possible to transfer data.
The source-code that I bring here will do exactly the same as DHCP does and it was implemented
using native .NET Sockets and Threading classes.
You may change the source-code to fill your needs when writing your own app and may
be very useful and save you time because already implements multi-threading and
Demo code are functional tested under .NET 3.5 and compiled using Visual Studio
2008. The code use only managed objects so probably will run under other Operating
Systems.
The “Server” and the “Client” demo app have the following behavior when run together:
- When started a BackgroundWorker is created act as multi-threaded server. So many
clients may use the service at the same time;
- The Auto Discovery Server will bind the port 18500/UDP and wait for incoming packets;
- If incoming client packet reach the server sending a dummy 3 byte packet that is
used as “Get Server Address” identifier the Server will reply a pre-configured IP Address and Port that could be used to disclose Web Service
HTTP address or any other usage;
- The client after broadcasting the packet will try to connect on the Host/Port received.
If it is unreachable will display a debug message on the console.
There are two classes implemented and may be ready to use on other project with little
or no adaptations.
namespace Bruno.Ratnieks.AutoDiscovery
public class AutoDiscoveryServer
public class AutoDiscoveryClient
For client usage the coder may instantiate the following class:
public class clientSide
{
public clientSide()
{
BackgroundWorker objWorkerServerDiscovery = new BackgroundWorker();
objWorkerServerDiscovery.WorkerReportsProgress = true;
objWorkerServerDiscovery.WorkerSupportsCancellation = true;
AutoDiscoveryClient svcAutoDiscoveryClient = new AutoDiscoveryClient(ref objWorkerServerDiscovery);
objWorkerServerDiscovery.DoWork += new DoWorkEventHandler(svcAutoDiscoveryClient.Start);
objWorkerServerDiscovery.ProgressChanged += new ProgressChangedEventHandler(logWorkers_ProgressChanged);
objWorkerServerDiscovery.RunWorkerAsync();
}
private void logWorkers_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Report thread messages to Console
Console.WriteLine(e.UserState.ToString());
}
}
By declaring the clientSide() on the application will be enough to start the working
thread and start look for a server on the network
clientSide cs = new clientSide();
Make sure the application will keep running because it work as backgroud thread.
This action may be simplified by the developer fairly easy. Just ignoring the backgroundWorker
code, removing reportProgress calls and calling the AutoDiscoveryClient.Start()
directly.
This code will make sure you console application will not exist during execution:
while (true)
{
// Do nothing and wait for any key to exit console application
Console.ReadLine();
return;
}
When the client receive data or need to output messages to the console it will use
the ReportProgress method provided by the BackgroundWorker class which raises
a new event and you may parse the string received on the method clientSide.logWorkers_ProgressChanged().
The incoming “UserState” will contain the message sent by the background thread.
Below you can see the server loading which share the same structure of the client:
public class serverSide
{
public serverSide(string serverIP, int serverPort)
{
BackgroundWorker workerAutoDiscovery = new BackgroundWorker();
workerAutoDiscovery.WorkerReportsProgress = true;
workerAutoDiscovery.WorkerSupportsCancellation = true;
AutoDiscoveryServer svcAutoDiscovery = new AutoDiscoveryServer(ref workerAutoDiscovery, serverIP, serverPort);
workerAutoDiscovery.DoWork += new DoWorkEventHandler(svcAutoDiscovery.Start);
workerAutoDiscovery.ProgressChanged += new ProgressChangedEventHandler(logWorkers_ProgressChanged);
workerAutoDiscovery.RunWorkerAsync();
}
private void logWorkers_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Report thread messages to Console
Console.WriteLine(e.UserState.ToString());
}
}
By creating a new instance of serverSide class the server will be automatically running
just like the client.
The difference is that you set on the constructor the IP Address and Port that will
be sent to client when a packet is received.
You may use the UDP channel to reply any kind of data to client. but usually TCP
is used for extensive data exchange for better error checking and packet delivery
safety. That’s why the server demo app reply an IP Address and Port.
So the client will know were to connect to continue the communication using the benefits
of TCP/IP.
The “UDP Broadcast” communication method will work in local networks as mentioned
before and will reach all computers so it’s not a good idea to use this kind
of network channel to send and receive sensitive data.
Declaring the serverSide code is pretty simple. Just a constructor that receive two
parameters.
// That info will be sent to client as our TCP SERVER -- Not implemented.
// Change here the IP and port.
serverSide ss = new serverSide("10.1.1.1", 80);
As explained previusly clients will receive the dummy IP 10.1.1.1 and port 80 and
try to connect using TCP protocol.
80 is the default HTTP port so Web Services servers may be auto discovered by using
the code I’m publishing here.
The core of the demo is located on the following namespace and you may download it
here with all other source code for immediate compile and run.
namespace Bruno.Ratnieks.AutoDiscovery
public class AutoDiscoveryServer
public class AutoDiscoveryClient
Below you see a print screen for client and server running at the same time:

The other classes used on the code are here:
// AutoDiscoveryClient Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.Threading;
using netService;
namespace Bruno.Ratnieks.AutoDiscovery
{
public class AutoDiscoveryClient
{
public bool disposing = false;
// Fixed Port for broadcast.
// You may change it but CLIENT and SERVER must be configured with the same port.
private int AutoDiscoveryPort = 18500;
// Specify timeout since UDP is a state-less protocol
// 5000ms - 5 seconds.
private int ServerSyncTimeout = 5000;
private int AutoDiscoveryTimeout = 10000;
// Sample byte sequency that Identify a Server Address Request. You may change on
the client-side also.
// Implementing other byte sequences for other actions are also valid. You as developer
may know that ;)
byte[] packetBytes = new byte[] { 0x1, 0x2, 0x3 };
// Do not change:
// We will set this variable when Auto Discovery server reply with ACK;
// Do not change. This will be set if Server is found and check for TCP Connections
is OK
public string ServerAddress = String.Empty;
public int ServerPort = 0;
private BackgroundWorker worker;
public AutoDiscoveryClient(ref BackgroundWorker worker)
{
this.worker = worker;
worker.ReportProgress(1, "AutoDiscovery::Started at " + AutoDiscoveryPort + "/UDP");
}
public void Start(object sender, DoWorkEventArgs e)
{
try
{
while (this.disposing == false)
{
// Must look for server.. Repeat until configured.
if (ServerAddress == String.Empty)
{
this.worker.ReportProgress(2, "AutoDiscovery::Looking for server..");
// Broadcast the query
sendBroadcastSearchPacket();
}
Thread.Sleep(3000);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public void Stop()
{
this.disposing = true;
}
// Here is to check if the server replied by Auto Discovery is Alive.
private bool serverIsReachable(string host, int port)
{
try
{
TcpClient handle = TCP_Client.Connect(new IPEndPoint(IPAddress.Parse(host), port), ServerSyncTimeout);
if (handle.Connected == true)
{
handle.Close();
return (true);
}
else
{
return (false);
}
}
catch (Exception e)
{
worker.ReportProgress(1, "AutoDiscovery::Connect Error:" + e.Message);
return (false);
}
}
private bool sendBroadcastSearchPacket()
{
bool returnVal = false;
UdpClient udp = new UdpClient();
udp.EnableBroadcast = true;
udp.Client.ReceiveTimeout = AutoDiscoveryTimeout;
IPEndPoint groupEP = new IPEndPoint(IPAddress.Parse("255.255.255.255"), AutoDiscoveryPort);
try
{
udp.Send(packetBytes, packetBytes.Length, groupEP);
byte[] receiveBytes = udp.Receive(ref groupEP);
string returnData = Encoding.ASCII.GetString(receiveBytes, 0, receiveBytes.Length);
if (returnData.Substring(0, 3) == "ACK")
{
string[] splitRcvd = returnData.Split(' ');
this.worker.ReportProgress(3, "AutoDiscovery::Response Server is " + splitRcvd[1] + ":" + splitRcvd[2]);
// Check if the server is reachable! Try to connect it using TCP.
if (serverIsReachable(splitRcvd[1], Convert.ToInt16(splitRcvd[2])))
{
ServerAddress = splitRcvd[1];
ServerPort = Convert.ToInt16(splitRcvd[2]);
returnVal = true;
}
else
{
this.worker.ReportProgress(3, "AutoDiscovery::WARNING Server found but is unreachable. Retrying..");
return (false);
}
}
else if (returnData.Substring(0, 3) == "NAK")
{
this.worker.ReportProgress(3, "AutoDiscovery::INVALID REQUEST");
}
else
{
this.worker.ReportProgress(3, "AutoDiscovery::Garbage Received?");
}
Console.WriteLine("Sleeping. No work to do.");
Thread.Sleep(3000);
}
catch (SocketException e)
{
this.worker.ReportProgress(1, "AutoDiscovery::Timeout. Retrying "+e.Message);
}
udp.Close();
return (returnVal);
}
}
}
// AutoDiscoveryServer Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.ComponentModel;
namespace Bruno.Ratnieks.AutoDiscovery
{
public class AutoDiscoveryServer
{
private System.ComponentModel.BackgroundWorker workerUDP;
// Port the UDP server will listen to broadcast packets from UDP Clients.
private int AutoDiscoveryPort = 18500;
// Sample byte sequency that Identify a Server Address Request. You may change on
the client-side also.
// Implementing other byte sequences for other actions are also valid. You as developer
may know that ;)
byte[] packetBytes = new byte[] { 0x1, 0x2, 0x3 };
// In the following example code we reply to incoming client an IP Address that
// Client must use as server for any purpose. (TCP Server not implemented)
public IPAddress addrDaemonListenIP;
// Which port we will broadcast as TCP Server (not implemented).
public int BroadCastDaemonPort = 0;
private bool disposing = false;
public AutoDiscoveryServer(ref BackgroundWorker workerUDP, string serverIP, int serverPort)
{
this.workerUDP = workerUDP;
this.BroadCastDaemonPort = serverPort;
this.addrDaemonListenIP = IPAddress.Parse(serverIP);
}
public void Stop()
{
workerUDP.CancelAsync();
workerUDP.Dispose();
this.disposing = true;
}
/// <summary>
/// Start the listener.
/// </summary>
public void Start(object sender, DoWorkEventArgs e)
{
try
{
this.workerUDP.ReportProgress(30, "AutoDiscovery::Service Listening " + this.AutoDiscoveryPort + "/UDP");
byte[] ReceivedData = new byte[1024];
// Local End-Point
IPEndPoint LocalEP = new IPEndPoint(IPAddress.Any, AutoDiscoveryPort);
IPEndPoint RemoteEP = new IPEndPoint(IPAddress.Any, 0);
UdpClient newsock = new UdpClient(LocalEP);
ReceivedData = newsock.Receive(ref RemoteEP);
IPEndPoint IncomingIP = (IPEndPoint)RemoteEP;
while (!disposing)
{
if (ReceivedData.SequenceEqual(packetBytes))
{
// Use ReportProgress from BackgroundWorker as communication channel between main app and
the worker thread.
this.workerUDP.ReportProgress(1, "Discovery from " + IncomingIP + "/UDP");
// Here we reply the Service IP and Port (TCP)..
// You must point to your server and service port. For example a webserver: sending
the correct IP and port 80.
byte[] packetBytesAck = Encoding.ASCII.GetBytes("ACK " + addrDaemonListenIP + " " + BroadCastDaemonPort); // Acknowledged
newsock.Send(packetBytesAck, packetBytesAck.Length, RemoteEP);
this.workerUDP.ReportProgress(1, "Answering(ACK) " + packetBytes.Length + " bytes to " + IncomingIP);
}
else
{
// Unknown packet type.
this.workerUDP.ReportProgress(1, "Answering(NAK) " + packetBytes.Length + " bytes to " + IncomingIP);
byte[] packetBytesNak = Encoding.ASCII.GetBytes("NAK"); // Not Acknowledged
newsock.Send(packetBytesNak, packetBytesNak.Length, RemoteEP);
}
ReceivedData = newsock.Receive(ref RemoteEP);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
There is one more class netService.TCP_Client that you may need to compile the client
that is inside the zip file.
Complete source-code download link:
http://eggheadcafe.com/FileUpload/-895676619_UDPAutoDiscovery1.0.zip
Note: I''ve used VS2008 C# with .NET 3.5 but 2.0 and greater will do the job. Older
versions of Visual Studio were tested and no problems at all to compile the code.
Best wishes,
Bruno Ratnieks
Sniffer Networks do Brasil Ltda.
Bruno@sniffer.net