/ broadcast

Network discovery using UDP Broadcast (Java)

The Problem

I have a Java server and a Java client running on the same network and the applications are not to be used outside a private network (not over internet).

So I used a static IP for the server, but what if I deploy my application? What if the network changes? That means I'll lose my connection to the server and I'll have to change the IP on the client side again.

Now that would be stupid. I want the client to "discover" the server on the network and connect with it.


The Solution

Using UDP packets and broadcasting them! This technique however is not optimal, but as long as we stay in one network this shouldn't be a problem.
UDP packets however are fairly easy to work with. So let's get started.


Still here? Let's do this!

Server implementation

First, Let's create the Java Singleton class that will execute the code on the server-side. This will be multi-threaded of course,  so we'll also implement "Runnable".

When we implement Runnable, we also have to override the Run method.

public class DiscoveryThread implements Runnable {

  @Override
  public void run() {
  }

  public static DiscoveryThread getInstance() {
    return DiscoveryThreadHolder.INSTANCE;
  }

  private static class DiscoveryThreadHolder {

    private static final DiscoveryThread INSTANCE = new DiscoveryThread();
  }

}

Ok, let's think about this. What do we have to do?

  1. Open a socket on the server that listens to the UDP requests. (I've chosen 8888)
  2. Make a loop that handles the UDP requests and responses
  3. Inside the loop, check the received UPD packet to see if it's valid
  4. Still inside the loop, send a response to the IP and Port of the received packet

That's it on the server side.

Now, we'll translate this into code.

DatagramSocket socket;

  @Override
  public void run() {
    try {
      //Keep a socket open to listen to all the UDP trafic that is destined for this port
      socket = new DatagramSocket(8888, InetAddress.getByName("0.0.0.0"));
      socket.setBroadcast(true);

      while (true) {
        System.out.println(getClass().getName() + ">>>Ready to receive broadcast packets!");

        //Receive a packet
        byte[] recvBuf = new byte[15000];
        DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
        socket.receive(packet);

        //Packet received
        System.out.println(getClass().getName() + ">>>Discovery packet received from: " + packet.getAddress().getHostAddress());
        System.out.println(getClass().getName() + ">>>Packet received; data: " + new String(packet.getData()));

        //See if the packet holds the right command (message)
        String message = new String(packet.getData()).trim();
        if (message.equals("DISCOVER_FUIFSERVER_REQUEST")) {
          byte[] sendData = "DISCOVER_FUIFSERVER_RESPONSE".getBytes();

          //Send a response
          DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, packet.getAddress(), packet.getPort());
          socket.send(sendPacket);

          System.out.println(getClass().getName() + ">>>Sent packet to: " + sendPacket.getAddress().getHostAddress());
        }
      }
    } catch (IOException ex) {
      Logger.getLogger(DiscoveryThread.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

A few notes: If you want to use strings as commands (like I do in this example), you have to trim the string before comparing it.

There, that's it for the server.

Client implementation

Now we have to write the code for the client. Again, let me sketch how we are going to work.

  1. Open a socket on a random port.
  2. Try to broadcast to the default broadcast address (255.255.255.255)
  3. Loop over all the computer's network interfaces and get their broadcast addresses
  4. Send the UDP packet inside the loop to the interface's broadcast address
  5. Wait for a reply
  6. When we have a reply, check to see if the package is valid
  7. When it's valid, get the package's sender IP address; this is the server's IP address
  8. CLOSE the socket! We don't want to leave open random ports on someone else's computer

On a side note, we don't close the socket on the server because the server will receive and send UPD packets until the server is closed. Closing the socket on the server means that we won't be able to discover it any more.

Wow, that was quite a lot. Now let's put this into code!

        // Find the server using UDP broadcast
        try {
          //Open a random port to send the package
          c = new DatagramSocket();
          c.setBroadcast(true);

          byte[] sendData = "DISCOVER_FUIFSERVER_REQUEST".getBytes();

          //Try the 255.255.255.255 first
          try {
            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("255.255.255.255"), 8888);
            c.send(sendPacket);
            System.out.println(getClass().getName() + ">>> Request packet sent to: 255.255.255.255 (DEFAULT)");
          } catch (Exception e) {
          }

          // Broadcast the message over all the network interfaces
          Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
          while (interfaces.hasMoreElements()) {
            NetworkInterface networkInterface = interfaces.nextElement();

            if (networkInterface.isLoopback() || !networkInterface.isUp()) {
              continue; // Don't want to broadcast to the loopback interface
            }

            for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
              InetAddress broadcast = interfaceAddress.getBroadcast();
              if (broadcast == null) {
                continue;
              }

              // Send the broadcast package!
              try {
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, broadcast, 8888);
                c.send(sendPacket);
              } catch (Exception e) {
              }

              System.out.println(getClass().getName() + ">>> Request packet sent to: " + broadcast.getHostAddress() + "; Interface: " + networkInterface.getDisplayName());
            }
          }

          System.out.println(getClass().getName() + ">>> Done looping over all network interfaces. Now waiting for a reply!");

          //Wait for a response
          byte[] recvBuf = new byte[15000];
          DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
          c.receive(receivePacket);

          //We have a response
          System.out.println(getClass().getName() + ">>> Broadcast response from server: " + receivePacket.getAddress().getHostAddress());

          //Check if the message is correct
          String message = new String(receivePacket.getData()).trim();
          if (message.equals("DISCOVER_FUIFSERVER_RESPONSE")) {
            //DO SOMETHING WITH THE SERVER'S IP (for example, store it in your controller)
            Controller_Base.setServerIp(receivePacket.getAddress());
          }

          //Close the port!
          c.close();
        } catch (IOException ex) {
          Logger.getLogger(LoginWindow.class.getName()).log(Level.SEVERE, null, ex);
        }

There, that's it!

I've given you pretty much all of the code, so it shouldn't be easy to implement.

Don't forget to run your DiscoveryThread!

Thread discoveryThread = new Thread(DiscoveryThread.getInstance());
discoveryThread.start();

~Michiel De Mey

Michiel De Mey

Michiel De Mey

Full-time geek, Full Stack Engineer and Full Metal Hero. NodeJs, AngularJs, API design, WebSockets, WebSec & IoT enthusiast. Former San Francisco resident.

Read More