April 11, 2009, 8:02 a.m.
posted by vendetta
Using Broadcast Packets to Advertise a Server
One of the most common uses for broadcast packets is to advertise the presence of a server on the network. By repeatedly sending out a broadcast packet containing information regarding the server at a predetermined time interval, you make it possible for clients to easily detect the presence of the server on the local subnet. This technique is used for many types of applications, from Windows application servers to network printers.
This section shows you a simple way to add broadcast advertising to the TcpChat program (Listing 9.7) presented in Chapter 9, "Using Threads." This version allows clients to know when another chat program is running on the same subnet.
The Advertising Loop
The easiest way to add advertising to a network application is to create a background thread that repeatedly broadcasts the server information at a set time interval. The trick is to repeat the broadcast often enough that new clients can quickly find the broadcasting server, but not so often that the local network gets flooded with broadcast packets. Often a one-minute interval is chosen as a compromise.
The advertising thread should be self-contained, creating its own socket and sending the broadcast packet at the predetermined interval. It should be created as a background thread and assigned a lower priority than the main program thread. This ensures that the advertising thread does not take away from the main program’s processing time and affect the program’s overall response.
The Advertise.cs program in Listing 10.4 creates a background thread that sends out a broadcast packet every minute with the hostname of the device running the application.
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; class Advertise { public static void Main() { Advertise server = new Advertise(); } public Advertise() { Thread advert = new Thread(new ThreadStart(sendPackets)); advert.IsBackground = true; advert.Start(); Console.Write("Press Enter to stop"); string data = Console.ReadLine(); } void sendPackets() { Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, 9050); string hostname = Dns.GetHostName(); byte[] data = Encoding.ASCII.GetBytes(hostname); while (true) { sock.SendTo(data, iep); Thread.Sleep(60000); } } }
The Advertise.cs program creates a new Thread object using the sendPackets() method, which generates a Socket object for the broadcast address and broadcasts the hostname every 60 seconds. The Thread.Sleep() method puts the thread in idle mode between broadcasts. The broadcast thread is placed in background mode by means of the IsBackground property of the Thread class.
Because the main part of the application does nothing but create the background thread, it must wait, or the background thread will be terminated. This is set up using the Console.ReadLine() method to wait for input from the console before terminating the main program.
You can watch the Advertise.cs program by running the RecvBroadcst program, which will show the first two broadcast messages.You can use the WinDump or Analyzer programs to watch how the broadcast messages are sent on the network. As expected, the program sends out a broadcast packet every 60 seconds identifying itself on the network. Any clients listening to broadcasts on port 9051 will receive the messages and know that the server is available on the network.
The TcpChat program (Listing 9.7) presented in Chapter 9 had one major drawback: for it to communicate properly, you had to manually compile the address of the remote host into the program—obviously not a good way to do business in a production application. With broadcast messages at your disposal, you can fix the TcpChat program to be more user-friendly.
There are two pieces that need to be added to the original TcpChat program to accomplish this goal:
-
A background thread that broadcasts an identification message on the subnet
-
A way to listen for broadcast messages and detect another chat application running on the subnet
As seen in the Advertise.cs program, the first part is easy; you just create a background thread using the Thread class to broadcast a message using a UDP port that’s separate from the one used for the chat communication. The second piece—the listener—is a little trickier to implement.
First, the program must listen for broadcast messages from possible chat clients on the subnet. This can be done by another background thread that listens for broadcasts on the network. As broadcast messages from other chat clients are detected, their information will be listed for the customer’s selection. A problem with creating this list is that each client is sending its information out every minute. You cannot simply add any message detected to the list, or a single chat client will appear multiple times. A production-quality application must keep track of each client added to the list and not duplicate it. For this simple example, you can handle that issue by having the program get only the first advertising chat program it sees and place its information in the ListBox.
The second part of this process is to enable the customer’s selection of the desired client information from the list box (which for this example will be only one entry). Then the customer needs to click the Connect button to chat with the selected client. The hostname and address for the remote client are taken from the information gathered from the broadcast packet, and the chat connection is created.
The NewTcpChat Program
All of these features are demonstrated in the NewTcpChat.cs program, shown in Listing 10.5.
using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
class NewTcpChat : Form
{
private static TextBox newText;
private static ListBox results;
private static ListBox hosts;
private static Socket client;
private static byte[] data = new byte[1024];
public NewTcpChat()
{
Text = "New TCP Chat Program";
Size = new Size(400, 380);
Label label1 = new Label();
label1.Parent = this;
label1.Text = "Enter text string:";
label1.AutoSize = true;
label1.Location = new Point(10, 30);
newText = new TextBox();
newText.Parent = this;
newText.Size = new Size(200, 2 * Font.Height);
newText.Location = new Point(10, 55);
results = new ListBox();
results.Parent = this;
results.Location = new Point(10, 85);
results.Size = new Size(360, 10 * Font.Height);
Label label2 = new Label();
label2.Parent = this;
label2.Text = "Active hosts";
label2.AutoSize = true;
label2.Location = new Point(10, 240);
hosts = new ListBox();
hosts.Parent = this;
hosts.Location = new Point(10, 255);
hosts.Size = new Size(360, 5 * Font.Height);
Button sendit = new Button();
sendit.Parent = this;
sendit.Text = "Send";
sendit.Location = new Point(220,52);
sendit.Size = new Size(5 * Font.Height, 2 * Font.Height);
sendit.Click += new EventHandler(ButtonSendOnClick);
Button connect = new Button();
connect.Parent = this;
connect.Text = "Connect";
connect.Location = new Point(295, 20);
connect.Size = new Size(6 * Font.Height, 2 * Font.Height);
connect.Click += new EventHandler(ButtonConnectOnClick);
Button listen = new Button();
listen.Parent = this;
listen.Text = "Listen";
listen.Location = new Point(295,52);
listen.Size = new Size(6 * Font.Height, 2 * Font.Height);
listen.Click += new EventHandler(ButtonListenOnClick);
Thread fh = new Thread(new ThreadStart(findHosts));
fh.IsBackground = true;
fh.Start();
}
void ButtonListenOnClick(object obj, EventArgs ea)
{
results.Items.Add("Listening for a client...");
Socket newsock = new Socket
(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9050);
newsock.Bind(iep);
newsock.Listen(5);
newsock.BeginAccept(new AsyncCallback(AcceptConn), newsock);
Thread advertise = new Thread(new ThreadStart(srvrAdvertise));
advertise.IsBackground = true;
advertise.Start();
}
void ButtonConnectOnClick(object obj, EventArgs ea)
{
results.Items.Add("Connecting...");
client = new Socket
(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
string selectedhost = (string)hosts.SelectedItem;
string[] hostarray = selectedhost.Split(':');
IPEndPoint iep = new IPEndPoint(IPAddress.Parse(hostarray[1]), 9050);
client.BeginConnect(iep, new AsyncCallback(Connected), client);
}
void ButtonSendOnClick(object obj, EventArgs ea)
{
byte[] message = Encoding.ASCII.GetBytes(newText.Text);
newText.Clear();
client.BeginSend(message, 0, message.Length, 0,
new AsyncCallback(SendData), client);
}
void AcceptConn(IAsyncResult iar)
{
Socket oldserver = (Socket)iar.AsyncState;
client = oldserver.EndAccept(iar);
results.Items.Add("Connection from: " + client.RemoteEndPoint.ToString());
Thread receiver = new Thread(new ThreadStart(ReceiveData));
receiver.IsBackground = true;
receiver.Start();
}
void Connected(IAsyncResult iar)
{
try
{
client.EndConnect(iar);
results.Items.Add("Connected to: " + client.RemoteEndPoint.ToString());
Thread receiver = new Thread(new ThreadStart(ReceiveData));
receiver.IsBackground = true;
receiver.Start();
} catch (SocketException)
{
results.Items.Add("Error connecting");
}
}
void SendData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int sent = remote.EndSend(iar);
}
void ReceiveData()
{
int recv;
string stringData;
while (true)
{
recv = client.Receive(data);
stringData = Encoding.ASCII.GetString(data, 0, recv);
if (stringData == "bye")
break;
results.Items.Add(stringData);
}
stringData = "bye";
byte[] message = Encoding.ASCII.GetBytes(stringData);
client.Send(message);
client.Close();
results.Items.Add("Connection stopped");
return;
}
void srvrAdvertise()
{
Socket server = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
server.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.Broadcast, 1);
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, 9051);
byte[] hostname = Encoding.ASCII.GetBytes(Dns.GetHostName());
while (true)
{
server.SendTo(hostname, iep);
Thread.Sleep(60000);
}
}
void findHosts()
{
while (true)
{
Socket remoteHosts = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, 9051);
EndPoint ep = (EndPoint)iep;
remoteHosts.Bind(iep);
byte[] data = new byte[1024];
int recv = remoteHosts.ReceiveFrom(data, ref ep);
string stringData = Encoding.ASCII.GetString(data, 0, recv);
string entry = stringData + ":" + ep.ToString();
if (!hosts.Items.Contains(entry))
hosts.Items.Add(""entry);
}
}
public static void Main()
{
Application.Run(new NewTcpChat());
}
}
The NewTcpChat program uses the basic form of the old TcpChat program, but it adds the new required pieces for broadcasting. When the Listen button is clicked to put the program in listen mode, a background thread starts. The srvrAdvertise() method implements a broadcast advertising program to announce the name of the system on which the chat program is running. This ensures that only chat clients in the listen mode will be advertised on the network.
The second enhancement in NewTcpChat is a new method, findHosts(),which listens for broadcasts on the UDP 9051 port. Because this method must be run in a background thread when the application starts, it is placed in the main program section.
Finally, notice that the Connect button method has been modified. Instead of connecting to a predetermined address, the selected entry from the host’s list box is retrieved and parsed to find the destination IP address, which is used in the connection attempt.
Testing the New Application
Unfortunately, because the NewTcpChat program automatically listens for broadcasts on UDP port 9051, you can only run one instance of the program on a machine. (Remember, only one program at a time can bind to a port). To test this program you must have two machines on the same subnet.
Start the NewTcpChat program in the command prompt window on two separate machines. On one, click the Listen button to place it in listen mode. The other program should eventually see the broadcast from the listening program and display that machine’s hostname along with the IP address and port number in the list box. Figure shows what this should look like.
When you see the host information in the list box, click once to select the information, and then click the Connect button. This should establish the chat connection with the remote chat program, and you can begin sending text messages.
