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.