Lekce 12 - Java server - Propagace lokální sítí (2. část)
V minulé lekci, Java server - Propagace lokální sítí (1. část), jsme rozpracovali zviditelnění našeho chat serveru v Javě klientům v lokální síti.
Dnes budeme pokračovat a upravíme zbylé třídy, kterým jsme upravili jejich rozhraní.
Úprava stávajících tříd
Začneme úpravou třídy ParameterFactory
. V první části
jsme přidali do rozhraní bezparametrickou metodu getParameters()
,
kterou nyní implementujeme:
@Singleton public class ParameterFactory implements IParameterFactory { private IParameterProvider parameterProvider; @Override public IParameterProvider getParameters() { if (parameterProvider == null) { throw new IllegalStateException("Je potřeba nejdříve inicializovat parametry."); } return parameterProvider; } @Override public IParameterProvider getParameters(String[] args) { if (parameterProvider == null) { parameterProvider = new CmdParser(args); } return parameterProvider; } }
Do třídy jsme přidali jednu instanční proměnnou
parameterProvider
typu IParameterProvider
. Tuto
proměnnou budeme inicializovat v parametrické metodě
getParameters()
. Toto řešení není nejideálnější, zato je
nejjednodušší. Jediné, co musíme dodržet, je, že se nejdříve zavolá
parametrická verze metody getParameters()
, a až poté můžeme
volat její bezparametrickou verzi.
Další na pořadí je třída ConnectionManager
, které jsme do
rozhraní přidali dvě nové metody: getConnectedClientCount()
a
getMaxClients()
. Implementace metod je následující:
@Override public int getConnectedClientCount() { return clients.size(); } @Override public int getMaxClients() { return maxClients; }
Počet připojených klientů získáme z velikosti kolekce
clients
. Maximální počet klientů je uložen v instanční
konstantě maxClients
.
Třetí třída, kterou je třeba upravit, je ServerThread
,
jejíž rozhraní nyní dědí z rozhraní ServerInfoProvider
. Toto
rozhraní vyžaduje implementaci metody
getServerStatusMessage()
:
@Override public IMessage getServerStatusMessage() { return null; }
Tělo metody necháme zatím prázdné. Později se k této metodě vrátíme a doplníme jej.
Spuštění multicast senderu
U třídy ServerThread
ještě chvilku zůstaneme, protože
odtud budeme spouštět třídu MulticastSender
. Do třídy
přidáme instanční konstantu typu IMulticastSender
, kterou
budeme inicializovat v konstruktoru z parametru:
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; }
Do konstruktoru serveru nepřidáme přímo rozhraní
IMulticastSender
, ale pouze jeho továrnu. V konstruktoru
inicializujeme multicastSender
z továrny metodou
getMulticastSender()
, které předáme jako parametr
this
, což je instance ServerThread
, která
implementuje rozhraní ServerInfoProvider
.
Spuštění a zastavení MulticastSender
u provedeme v metodě
run()
:
@Override public void run() { // Začátek metody multicastSender.start(); // Spuštění multicast senderu connectionManager.onServerStart(); // ... // konec metody multicastSender.shutdown(); connectionManager.onServerStop(); }
Tím, že jsme upravili konstruktor třídy ServerThread
, jsme
si rozbili továrnu pro instanciování této třídy. Pojďme to napravit.
Třídě ServerThreadFactory
přidáme novou instanční konstantu
typu IMulticastSenderFactory
, kterou budeme inicializovat v
konstruktoru třídy z parametru:
private final IMulticastSenderFactory multicastSenderFactory; @Inject public ServerThreadFactory(IConnectionManagerFactory connectionManagerFactory, IMulticastSenderFactory multicastSenderFactory) { this.connectionManagerFactory = connectionManagerFactory; this.multicastSenderFactory = multicastSenderFactory; }
V metodě getServerThread()
pouze přidáme továrnu na
správné místo jako parametr:
return new ServerThread(connectionManagerFactory.getConnectionManager(maxClients, waitingQueueSize), multicastSenderFactory, port);
ServerStatusMessage
Nyní vytvoříme zprávu, která bude obsahovat informace o stavu serveru. Zpráva bude obsahovat celkem 6 atributů:
serverID
- id serveruserverStatus
- stav serveru (prázdný, plný, má místo)clientCount
- počet připojených klientůmaxClients
- maximální počet klientůserverName
- název serveruport
- port, na kterém server naslouchá
Třídu nazvěme ServerStatusMessage
a necháme ji implementovat
rozhraní IMessage
:
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); } } }
Rozhraní IMessage
nám předepisuje implementovat metody
getType()
a getData()
. Metoda getType()
vrací konstantu MESSAGE_TYPE
. Metoda getData()
vrací
objekt ServerStatusData
, který obsahuje samotné informace o
serveru. Třída ServerStatusData
je přepravka, to znamená, že
pouze zapouzdří informace pod jednu třídu. Třída musí obsahovat takové
datové typy, které jsou serializovatelné, jinak by se vyvolala
vyjímka při posílání objektu po síti.
Tvorba zprávy s informacemi o serveru
Nyní se vrátíme k metodě getServerStatusMessage()
a
doplníme ji o tělo:
@Override public IMessage getServerStatusMessage() { final int connectedClients = connectionManager.getConnectedClientCount(); final int maxClients = connectionManager.getMaxClients(); return new ServerStatusMessage(new ServerStatusData( ID, connectedClients, maxClients, serverName, port)); }
V metodě získáme z ConnectionManager
u informace o počtu
aktuálně připojených klientů a maximální počet připojených klientů.
Statickou konstantu ID
musíme vytvořit. Umístěte ji do třídy
ServerThread
mezi definici ostatních instančních konstant:
private static final UUID ID = UUID.randomUUID();
Instanční konstantu name
také musíme vytvořit. Bude se
inicializovat v konstruktoru třídy z parametru:
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; }
Instanční konstanta port
již byla přítomna.
Opět jsme změnili konstruktor třídy ServerThread
, takže
musíme upravit i její továrnu:
@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); }
V továrně do metody getServerThread()
přidáme proměnnou
serverName
, kterou inicializujeme z parametrů. Tuto proměnnou
přidáme na správné místo při vytváření nové instance.
Timto bychom byli hotovi s implementací propagace serveru v lokální síti.
V poslední části, Java server - Propagace lokální sítí (3. část), si vytvoříme na klientské straně třídu, která bude číst tyto informace. V budoucnu pak tuto třídu použijeme při implementaci chatu.