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

Lesson 12 - Java Server - Local Area Network Propagation (Part 2)

In the previous lesson, Java Server - Local Area Network Propagation (Part 1), we started working on the visibility of our Java chat server to local network clients.

Today we're going to continue modifying the remaining classes, whose interfaces we modified.

Modifying Existing Classes

We'll start by editing the ParameterFactory class. In the first part, we added the non-parametric getParameters() method to the interface, which we'll now implement:

@Singleton
public class ParameterFactory implements IParameterFactory {

    private IParameterProvider parameterProvider;

    @Override
    public IParameterProvider getParameters() {
        if (parameterProvider == null) {
            throw new IllegalStateException("Initialize parameters first.");
        }
        return parameterProvider;
    }

    @Override
    public IParameterProvider getParameters(String[] args) {
        if (parameterProvider == null) {
            parameterProvider = new CmdParser(args);
        }

        return parameterProvider;
    }
}

We added one instance variable, parameterProvider, of the IParameterProvider type. We'll initialize this variable in the parametric getParameters() method. This solution isn't ideal, but it's the simplest. All we have to do is call the parametric version of the getParameters() method first, and then we may call its parameterless version.

The next class is ConnectionManager for which we added two new methods to the interface: getConnectedClientCount() and getMaxClients(). The implementation of the methods is as follows:

@Override
public int getConnectedClientCount() {
    return clients.size();
}

@Override
public int getMaxClients() {
    return maxClients;
}

We get the number of connected clients from the size of the clients collection. The maximum number of clients is stored in the maxClients instance constant.

The third class to edit is ServerThread, whose interface now inherits from the ServerInfoProvider interface. This interface requires the getServerStatusMessage() method to be implemented:

@Override
public IMessage getServerStatusMessage() {
    return null;
}

We leave the method body empty for now. We'll return to this method later and add it.

Starting the Multicast Sender

We'll stick with ServerThread for a while, because we'll run the MulticastSender class from there. We'll add an instance constant of the IMulticastSender type to the class and initialize it in the constructor from a parameter:

private final IMulticastSender multicastSender;
@Inject
ServerThread(IConnectionManager connectionManager,
    IMulticastSenderFactory multicastSenderFactory, int port) {
    super("ServerThread");
    this.connectionManager = connectionManager;
    this.multicastSender = multicastSenderFactory.getMulticastSender(this);
    this.port = port;
}

We don't add the IMulticastSender interface to the server constructor directly, but only its factory. In the constructor, we initialize multicastSender using the factory and its getMulticastSender() method, to which we pass this as a parameter. this which is a ServerThread instance that implements the ServerInfoProvider interface.

We'll start and stop MulticastSender in the run() method:

@Override
public void run() {
    // Method start
    multicastSender.start(); // Starting the multicast sender
    connectionManager.onServerStart();
    // ...
    // Method end
    multicastSender.shutdown();
    connectionManager.onServerStop();
}

By modifying the ServerThread constructor we broke the factory to instantiate this class. Let's fix it. We'll add a new instance constant of the IMulticastSenderFactory type to the ServerThreadFactory and initialize it in the class constructor from a parameter:

private final IMulticastSenderFactory multicastSenderFactory;
@Inject
public ServerThreadFactory(IConnectionManagerFactory connectionManagerFactory,
    IMulticastSenderFactory multicastSenderFactory) {
    this.connectionManagerFactory = connectionManagerFactory;
    this.multicastSenderFactory = multicastSenderFactory;
}

In the getServerThread() method, we'll just add the factory to the right place as a parameter:

return new ServerThread(connectionManagerFactory.getConnectionManager(maxClients, waitingQueueSize),
    multicastSenderFactory, port);

ServerStatusMessage

Now we'll create a message that will contain the server status information. The message will contain a total of 6 attributes:

  • serverID - server id
  • serverStatus - server status (empty, full, have space)
  • clientCount - number of connected clients
  • maxClients - maximum number of clients
  • serverName - server name
  • port - port on which the server listens

We'll name the class ServerStatusMessage and let it implement the IMessage interface:

public class ServerStatusMessage implements IMessage {
    private static final long serialVersionUID = -1429760060957272567L;
    public static final String MESSAGE_TYPE = "server-status";
    private final ServerStatusData statusData;
    public ServerStatusMessage(ServerStatusData statusData) {
        this.statusData = statusData;
    }

    @Override
    public String getType() {return MESSAGE_TYPE;}

    @Override
    public Object getData() {return statusData;}

    @Override
    public String toString() {return String.valueOf(getData());}

    public static final class ServerStatusData implements Serializable {
        private static final long serialVersionUID = -4288671744361722044L;

        public enum ServerStatus {EMPTY, HAVE_SPACE, FULL}

        public final UUID serverID;
        public final ServerStatus serverStatus;
        public final int clientCount;
        public final int maxClients;
        public final String serverName;
        public final int port;

        public ServerStatusData(UUID serverID, int clientCount,
            int maxClients, String serverName, int port) {
            this.serverID = serverID;
            this.serverStatus = serverStatus;
            this.clientCount = clientCount;
            this.maxClients = maxClients;
            this.serverName = serverName;
            this.port = port;

            final int delta = maxClients - clientCount;
            ServerStatus status = ServerStatus.EMPTY;
            if (delta == 0) {
                status = ServerStatus.FULL;
            } else if (delta > 0 && delta < maxClients) {
                status = ServerStatus.HAVE_SPACE;
            }
            this.serverStatus = status;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ServerStatusData that = (ServerStatusData) o;
            return clientCount == that.clientCount &&
                maxClients == that.maxClients &&
                port == that.port &&
                Objects.equals(serverID, that.serverID) &&
                serverStatus == that.serverStatus &&
                Objects.equals(serverName, that.serverName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(serverID, serverStatus, clientCount, maxClients, serverName, port);
        }

        @Override
        public String toString() {
            return String.format("%s: %d/%d - %s; port=%d", serverName, clientCount, maxClients, serverStatus, port);
        }
    }
}

The IMessage interface requires us to implement the getType() and getData() methods. The getType() method returns the MESSAGE_TYPE constant. The getData() method returns a ServerStatusData object that contains the server information. The ServerStatusData class is a container class so it only encapsulates information under one class. The class must contain data types that are serializable, otherwise an exception would be thrown when sending the object over the network.

Creating the Server Information Message

Now let's get back to the getServerStatusMessage() method and add its body:

@Override
public IMessage getServerStatusMessage() {
    final int connectedClients = connectionManager.getConnectedClientCount();
    final int maxClients = connectionManager.getMaxClients();

    return new ServerStatusMessage(new ServerStatusData(
        ID, connectedClients, maxClients, serverName, port));
}

In the method, we obtain information about the number of currently connected clients and the maximum number of clients from ConnectionManager. We must create the static ID constant and place it in the ServerThread class in the definition of the other instance constants:

private static final UUID ID = UUID.randomUUID();

We must also create the name instance constant. It'll be initialized in the class constructor from a parameter:

private final String serverName;
@Inject
ServerThread(IConnectionManager connectionManager,
    IMulticastSenderFactory multicastSenderFactory, String serverName, int port) {
    super("ServerThread");
    this.connectionManager = connectionManager;
    this.multicastSender = multicastSenderFactory.getMulticastSender(this);
    this.serverName = serverName;
    this.port = port;
}

The port instance constant was already there.

Again, we changed the constructor of the ServerThread class, so we have to modify its factory too:

@Override
public IServerThread getServerThread(IParameterProvider parameters) {
    final String serverName = parameters.getString(CmdParser.SERVER_NAME, DEFAULT_SERVER_NAME);
    final int port = parameters.getInteger(CmdParser.PORT, DEFAULT_SERVER_PORT);
    final int maxClients = parameters.getInteger(CmdParser.CLIENTS, DEFAULT_MAX_CLIENTS);
    final int waitingQueueSize = parameters.getInteger(CmdParser.MAX_WAITING_QUEUE, DEFAULT_WAITING_QUEUE_SIZE);

    return new ServerThread(connectionManagerFactory.getConnectionManager(maxClients, waitingQueueSize),
        multicastSenderFactory, serverName, port);
}

In the factory, we pass the serverName variable to the getServerThread() method, initializing it from the parameters. We added this variable to the right place when creating a new instance.

This would be all for implementing the propagation of the server in the local network.

In the next lesson, Java Server - Local Area Network Propagation (Part 3), we'll finish propagating our Java server and create a client-side class to receive datagram packets that the server sends to be visible.


 

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