NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Lekce 11 - Java server - Propagace lokální sítí (1. část)

V předchozím kvízu, Kvíz - Komunikační protokol, Event bus a pluginy v Javě, jsme si ověřili nabyté zkušenosti z předchozích lekcí.

Dnes se postaráme, aby byl server viditelný v lokální síti.

TCP vs. UDP

Než začneme programovat, povíme si trochu teorie o TCP a UDP protokolech.

TCP

Zkratka znamená Transmission Control Protocol. Jedná se o protokol, který je spolehlivý a spojovaný. Spolehlivý znamená, že data, která odešle jeden uživatel, dorazí k cíli v pořádku a ve správném pořadí. Spojovaný znamená, že před začátkem komunikace se musí vytvořit spojení, které se drží po celou dobu. Používá se hlavně tam, kde dáváme přednost spolehlivosti před rychlostí.

UDP

Zkratka znamená Universal/User Datagram Protocol. UDP je přesný opak TCP. Protokol je nespolehlivý a nespojovaný. Jednotlivé datagramy mohou přicházet v různém pořadí. Protokol nezaručuje, že se data úspěšně přenesou - mohou se cestou ztratit. Používá se tam, kde je potřeba efektivně a rychle přenášet data, jako jsou hry, videa...

Multicast sender

V předchozích lekcích jsme navrhli komunikační protokol právě nad TCP, takže máme zaručeno, že data vždy dorazí v pořádku. Nyní implementujeme zviditelnění serveru pomocí UDP. Vytvoříme si novou třídu, která bude v nekonečné smyčce v definovaném intervalu rozesílat datagram všem strojům v lokální síti. Stroj, který nebude vědět, jak zprávu zpracovat, ji zahodí. My si napíšeme v klientovi obsluhu na příjem těchto datagramů.

V balíčku core vytvoříme nový balíček multicaster, ve kterém implementujeme výše zmíněnou funkcionalitu.

Návrh rozhraní

Vytvoříme si jednoduché značkovací rozhraní IMulticastSender, které nebude obsahovat žádnou metodu:

public interface IMulticastSender extends IThreadControl {}

Dále rozhraní představující továrnu pro tvorbu instancí IMulticastSenderFactory s metodou getMulticastSender():

public interface IMulticastSenderFactory {
    IMulticastSender getMulticastSender(ServerInfoProvider serverInfoProvider);
}

V metodě getMulticastSender() jsme použili dosud nevytvořené rozhraní ServerInfoProvider, které bude představovat rozhraní na získání informace o aktuálním stavu serveru (identifikátor, obsazenost, adresu, název...). Pojďme jej přidat:

public interface ServerInfoProvider {
    IMessage getServerStatusMessage();
}

Rozhraní obsahuje jedinou metodu getServerStatusMessage(), která bude vracet zprávu s informacemi o stavu serveru.

Úprava stávajících rozhraní

Nyní přidáme nové metody do již existujících rozhraní, které v budeme potřebovat při implementaci. Rozhraní IParameterFactory rozšíříme o bezparametrickou metodu getParameters():

public interface IParameterFactory {
    IParameterProvider getParameters(); // Nově přidaná metoda
    IParameterProvider getParameters(String[] args);
}

Do rozhraní IConnectionManager přidáme metodu na získání počtu připojených klientů getConnectedClientCount() a metodu getMaxClients(), která vrátí maximální počet připojených klientů:

public interface IConnectionManager {
    void addClient(Socket socket) throws IOException;
    void onServerStart();
    void onServerStop();
    int getConnectedClientCount(); // Nově přidaná metoda
    int getMaxClients();           // Nově přidaná metoda
}

Rozhraní IMessage rozšíříme o defaultní metodu toByteArray(), která vytvoří ze třídy serializovaný balík dat:

default byte[] toByteArray() throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(this);
    oos.writeByte(0);
    final byte[] bytes = baos.toByteArray();
    assert bytes.length < 1024;

    return bytes;
}

V metodě vytváříme instanci třídy ByteArrayOutputStream, kterou předáme jako parametr při vytváření instance ObjectOutputStream. Metodou writeObject() serializujeme naší zprávu a data zapíšeme do streamu. Je nutné přidat ještě nulový byte, protože jinak by stream na druhé straně nerozpoznal, kde data končí. Metodou toByteArray() získáme výsledný balík dat. Přidal jsem ještě kontrolu, aby data nepřesáhla délku 1024. Až budeme implementovat klienta, tak buffer, do kterého budeme číst data, bude mít právě velikost 1024 bajtů.

Nakonec upravíme rozhraní IServerThread tak, aby dědilo ještě z rozhraní ServerInfoProvider:

public interface IServerThread extends IThreadControl, ServerInfoProvider {}

Implementace rozhraní

Když jsme vytvořili a upravili potřebná rozhraní, pojďme je naimplementovat. Nejdříve vytvoříme implementaci rozhraní IMulticastSender pomocí třídy MulticastSender:

class MulticastSender extends Thread implements IMulticastSender {

}

Do třídy přidáme tři konstanty:

private static final long SLEEP_TIME = 2000L;
private static final String DEFAULT_MULTICAST_ADDRESS = "224.0.2.50";
private static final int DEFAULT_MULTICAST_PORT = 56489;

Na okamžik bych se zastavil u výchozí multicastové adresy. Adresa 224.0.2.50 spadá do multicast rozsahu 224.0.2.0 - 224.0.255.255. Packety odesílané v tomto adresním rozsahu budou putovat napříč celou lokální sítí.

Instanční konstanty budou celkem dvě:

private final IParameterFactory parameterFactory;
private final ServerInfoProvider serverInfoProvider;

Instanční proměnné budou čtyři:

private DatagramSocket socket;
private InetAddress broadcastAddress;
private int port;
private boolean interrupt = false;
  • DatagramSocket reprezentuje socket, pomocí kterého budeme odesílat datagramové packety.
  • InetAddress obsahuje broadcastovací adresu, na které budeme naše packet odesílat.
  • Proměnná interrupt má stejný význam, jako v předchozích kapitolách.

Konstruktor třídy bude neveřejný, dostupný pouze v rámci svého balíčku a bude přijímat dva parametry typu IParameterFactory a ServerInfoProvider:

MulticastSender(IParameterFactory parameterFactory, ServerInfoProvider serverInfoProvider) {
    super("MulticastSender");
    this.parameterFactory = parameterFactory;
    this.serverInfoProvider = serverInfoProvider;
}

V konstruktoru nejdříve nastavíme název vlákna, abychom ho mohli v budoucnu snadno rozlišit. Pak se inicializují instanční konstanty.

Dále vytvoříme privátní metodu, ve které budeme inicializovat adresu a socket. Metodu nazveme init():

private void init() {
    final IParameterProvider parameterProvider = parameterFactory.getParameters();
    try {
        this.broadcastAddress = InetAddress.getByName(parameterProvider
            .getString(CmdParser.MULTICAST_ADDRESS, DEFAULT_MULTICAST_ADDRESS));
        this.port = parameterProvider.getInteger(CmdParser.MULTICAST_PORT, DEFAULT_MULTICAST_PORT);
        this.socket = new DatagramSocket();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

Nejdříve se získá instance typu IParameterProvider z továrny pomocí bezparametrické metody getParameters(). Z parametrů získáme hodnotu broadcastovací adresy. Pokud hodnota nebude k dispozici, použijeme výchozí hodnotu. Do třídy CmdParser si prosím přidejte dva nové atributy: MULTICAST_ADDRESS a MULTICAST_PORT s vlastními hodnotami:

// Adresa, na které se budou vysílat multicastové packety
public static final String MULTICAST_ADDRESS = "multicast_address";
// Port, na kterém se budou vysílat multicastové packety
public static final String MULTICAST_PORT = "multicast_port";

Nyní budeme implementovat, případně přepisovat metody, které nám definuje rozhraní IThreadControl, případně třída Thread. Přepíšeme metodu start(), kterou vybavíme voláním metody init():

@Override
public synchronized void start() {
    init();
    super.start();
}

Metoda shutdown() bude mít stejné tělo, jako v mnoha ostatních případech:

@Override
public void shutdown() {
    interrupt = true;
    try {
        join();
    } catch (InterruptedException ignored) { }
}

Nejdůležitější metodu run() jsem si nechal nakonec:

public void run() {
    if (socket == null || broadcastAddress == null) {
        interrupt = true;
    }

    while(!interrupt) {
        try {
            final IMessage serverStatusMessage = serverInfoProvider
                .getServerStatusMessage();
            final byte[] data = serverStatusMessage.toByteArray();
            final DatagramPacket datagramPacket = new DatagramPacket(
                data, data.length, broadcastAddress, port);
            this.socket.send(datagramPacket);
        } catch (IOException e) {
            e.printStackTrace();
            break;
        }

        try {
            Thread.sleep(SLEEP_TIME);
        } catch (InterruptedException ignored) {}
    }
}

Na začátku metody se zjistí, zda-li proběhla inicializace socketu a adresy úspěšně. Pokud jedna z proměnných bude mít hodnotu null, nastaví se proměnná interrupt na hodnotu true, čímž se zajistí, že se vlákno ukončí. V nekonečné smyčce se získá zpráva s informacemi o serveru a převede se na balík dat. Tento balík dat se vloží do datagramu a socketem se odešle do světa. Následuje uspání vlákna na hodnotu konstanty SLEEP_TIME. Touto nekonečnou smyčkou zajistíme, že se náš server zviditelní napříč celou lokální sítí.

Jsme téměř na konci lekce, ale ještě stihneme vytvořit továrnu. Vytvoříme tedy třídu MulticastSenderFactory, která implementuje rozhraní IMulticastSenderFactory. Rozhraní vyžaduje, aby třída obsahovala jedinou metodu getMulticastSender():

@Singleton
public class MulticastSenderFactory implements IMulticastSenderFactory {

    private final IParameterFactory parameterFactory;

    @Inject
    public MulticastSenderFactory(IParameterFactory parameterFactory) {
        this.parameterFactory = parameterFactory;
    }

    @Override
    public IMulticastSender getMulticastSender(ServerInfoProvider serverInfoProvider) {
        return new MulticastSender(parameterFactory, serverInfoProvider);
    }
}

Nakonec zaregistrujeme továrnu v modulu ServerModule:

bind(IMulticastSenderFactory.class).to(MulticastSenderFactory.class);

To by bylo z první části dnešní lekce vše.

V druhé části, Java server - Propagace lokální sítí (2. část), naimplementujeme zbytek funkcionality na serverové části.


 

Předchozí článek
Kvíz - Komunikační protokol, Event bus a pluginy v Javě
Všechny články v sekci
Server pro klientské aplikace v Javě
Přeskočit článek
(nedoporučujeme)
Java server - Propagace lokální sítí (2. část)
Článek pro vás napsal Petr Štechmüller
Avatar
Uživatelské hodnocení:
1 hlasů
Autor se věnuje primárně programování v Javě, ale nebojí se ani webových technologií.
Aktivity