Get up to 80 % extra points for free! More info:

Lesson 13 - Java Server - Local Area Network Propagation (Part 3)

In the previous lesson, Java Server - Local Area Network Propagation (Part 2), we taught our server to be visible for clients in the network.

Today we're going to teach our clients to respond to the server communication. On the client side, we'll create a class that will capture the data sent by the server.

Searching for Local Servers

It won't be searching literally, because the servers will actively broadcast packets of information about themselves. In the client module we'll create a new LanServerFinder class, into which we'll implement receiving packets from the server.

Class Implementation

We'll just let the class implement the Runnable interface so that the client can decide itself what thread the code will run in:

public class LanServerFinder implements Runnable {

}

We'll add one MulticastSocket instance variable to the class:

private final MulticastSocket socket;

Using this socket we'll receive datagrams from the server.

Next, we'll add two instance variables:

private OnServerFoundListener serverFoundListener;
private boolean interrupt = false;

We'll create the OnServerFoundListener class in a moment. The interrupt variable has the same meaning as in the previous lessons.

The class constructor will have two parameters: broadcastAddress and port:

public LanServerFinder(InetAddress broadcastAddress, int port) throws IOException {
    this.socket = new MulticastSocket(port);
    this.socket.setSoTimeout(5000);
    this.socket.joinGroup(broadcastAddress);
}

In the constructor, we create a new MulticastSocket class instance that will listen on the defined port. The setSoTimeout() method will cause the SocketTimeoutException being thrown every 5 seconds. Use the joinGroup() method we set the broadcast address of the socket.

We'll add a public shutdown() method to the class to terminate the thread:

public void shutdown() {
    interrupt = true;
}

Next, we'll add the getters and setters for the OnServerFoundListener listener:

public OnServerFoundListener getServerFoundListener() {
    return serverFoundListener;
}

public void setServerFoundListener(OnServerFoundListener serverFoundListener) {
    this.serverFoundListener = serverFoundListener;
}

Now we'll finally create the internal OnServerFoundListener interface:

@FunctionalInterface
public interface OnServerFoundListener {
    void onServerFound(ServerStatusData data);
}

It's a functional interface with a single onServerFound() method, which we'll call whenever a new datagram with the server status information arrives.

Finally, we'll implement the run() method:

@Override
public void run() {
    final byte[] data = new byte[1024];
    final DatagramPacket datagramPacket = new DatagramPacket(data, data.length);

    while(!interrupt) {
        try {
            socket.receive(datagramPacket);
        } catch (SocketTimeoutException e) {
            continue;
        } catch (IOException e) {
            break;
        }

        final ByteArrayInputStream bais = new ByteArrayInputStream(
            datagramPacket.getData(),
            datagramPacket.getOffset(),
            datagramPacket.getLength());
        try {
            final ObjectInputStream ois = new ObjectInputStream(bais);
            final ServerStatusMessage statusMessage = (ServerStatusMessage) ois.readObject();
            final ServerStatusData statusData = (ServerStatusData) statusMessage.getData();
            if (serverFoundListener != null) {
                serverFoundListener.onServerFound(statusData);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

At the beginning of the method, the data read buffer is initialized to 1024 bytes and datagramPacket to be received is also initialized. In an infinite loop, we checked whether the thread should be terminated. If the interrupt variable is set to true, the infinite loop and thus the thread in which the code is running is terminated. We call the receive() method in the loop body to attempt to receive data from the server. The method is blocking, so the thread is blocked until data arrives, or in our case for five seconds, because then a SocketTimeoutException is thrown. When the data is successfully received, a new ByteArrayInputStream class instance is created, into which the serialized data of the received class is loaded. This instance is passed as a parameter when creating an ObjectInputStream class instance. We deserialize the received message from ObjectInputStream and cast it to ServerStatusMessage. From this class we get the ServerStatusData class. Finally, if the listener is set, the onServerFound() method is called with the statusData parameter. This ensures that the class will only receive datagrams, but leave them to be processed by another class.

You should have sufficient knowledge to test the functionality. If you don't know what to do, use the comments section below.

By this we finished promoting the server in the local network.

Next time, in the lesson Java Server - Plugin System Improvements, we'll still stick with the server-side. We'll improve the plugin system by loading external plugins and implement prioritized plugin initialization.


 

Previous article
Java Server - Local Area Network Propagation (Part 2)
All articles in this section
Server for Client Applications in Java
Skip article
(not recommended)
Java Server - Plugin System Improvements
Article has been written for you by Petr Štechmüller
Avatar
User rating:
1 votes
Activities