IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

11. diel - Java server - Propagácia lokálnou sieťou (1. časť)

V minulej lekcii, Java server - Systém pluginov , sme sa venovali systému pluginov nášho Java servera. Dnes sa postaráme, aby bol server viditeľný v lokálnej sieti.

Tcp vs. UDP

Než začneme programovať, povieme si trochu teórie o TCP a UDP protokoloch.

Tcp

Skratka znamená Transmission Control Protocol. Jedná sa o protokol, ktorý je spoľahlivý a spojovaný. Spoľahlivý znamená, že dáta, ktoré odošle jeden používateľ, dorazí k cieľu v poriadku a v správnom poradí. Spojovaný znamená, že pred začiatkom komunikácia sa musí vytvoriť spojenie, ktoré sa drží po celú dobu. Používa sa hlavne tam, kde dávame prednosť spoľahlivosti pred rýchlosťou.

UDP

Skratka znamená Universal / User Datagram Protocol. UDP je presný opak TCP. Protokol je nespoľahlivý a spojované. Jednotlivé datagramy môžu prichádzať v rôznom poradí. Protokol nezaručuje, že sa dáta úspešne prenesú - môžu sa cestou stratiť. Používa sa tam, kde je potreba efektívne a rýchlo prenášať dáta, ako sú hry, videá ...

Multicast sender

V predchádzajúcich lekciách sme navrhli komunikačný protokol práve nad TCP, takže máme zaručené, že dáta vždy dorazí v poriadku. Teraz implementujeme zviditeľnenie servera pomocou UDP. Vytvoríme si novú triedu, ktorá bude v nekonečnej slučke v definovanom intervale rozosielať datagram všetkým strojom v lokálnej sieti. Stroj, ktorý nebude vedieť, ako správu spracovať, ju zahodí. My si napíšeme v klientovi obsluhu na príjem týchto datagramov.

V balíčku core vytvoríme nový balík multicaster, v ktorom implementujeme vyššie zmienenú funkcionalitu.

Návrh rozhrania

Vytvoríme si jednoduché značkovacie rozhranie IMulticastSender, ktoré nebude obsahovať žiadnu metódu:

public interface IMulticastSender extends IThreadControl {}

Ďalej rozhranie predstavujúce továreň pre tvorbu inštancií IMulticastSenderFactory s metódou getMulticastSender():

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

V metóde getMulticastSender() sme použili doteraz nie vytvorené rozhranie ServerInfoProvider, ktoré bude predstavovať rozhranie na získanie informácie o aktuálnom stave servera (identifikátor, obsadenosť, adresu, názov ...):

public interface ServerInfoProvider {
    IMessage getServerStatusMessage();
}

Rozhranie obsahuje jedinú metódu getServerStatusMessage(), ktorá bude vracať správu s informáciami o stave servera.

Úprava existujúcich rozhranie

Teraz pridáme nové metódy do už existujúcich rozhrania, ktoré v budeme potrebovať pri implementácii. Rozhranie IParameterFactory rozšírime o bezparametrickou metódu getParameters():

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

Do rozhrania IConnectionManager pridáme metódu na získanie počtu pripojených klientov getConnectedClientCount() a metódu getMaxClients(), ktorá vráti maximálny počet pripojených klientov:

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
}

Rozhranie IMessage rozšírime o defaultný metódu toByteArray(), ktorá vytvorí z triedy serializovaný balík dát:

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 metóde vytvárame inštanciu triedy ByteArrayOutputStream, ktorú odovzdáme ako parameter pri vytváraní inštancie ObjectOutputStream. Metódou writeObject() serializujeme našej správu a dáta zapíšeme do streamu. Je nutné pridať ešte nulový bajt, pretože inak by stream na druhej strane nerozpoznal, kde dáta končí. Metódou toByteArray() získame výsledný balík dát. Pridal som ešte kontrolu, aby dáta nepresiahla dĺžku 1024. Až budeme implementovať klienta, tak buffer, do ktorého budeme čítať dáta, bude mať práve veľkosť 1024 bajtov.

Nakoniec upravíme rozhranie IServerThread tak, aby dedilo ešte z rozhrania ServerInfoProvider:

public interface IServerThread extends IThreadControl, ServerInfoProvider {}

Implementácia rozhrania

Keď sme vytvorili a upravili potrebná rozhranie, poďme je naimplementovať. Najskôr vytvoríme implementáciu rozhrania IMulticastSender pomocou triedy MulticastSender:

class MulticastSender extends Thread implements IMulticastSender {}

Do triedy pridáme tri konštanty:

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 okamih by som sa zastavil pri predvolené multicastovej adresy. Adresa 224.0.2.50 spadá do multicast rozsahu 224.0.2.0 - 224.0.255.255. Packety odosielané v tomto adresnom rozsahu budú putovať naprieč celou lokálnou sieťou.

Inštančný konštanty budú celkom dve:

private final IParameterFactory parameterFactory;
private final ServerInfoProvider serverInfoProvider;

Inštančný premenné budú štyri:

private DatagramSocket socket;
private InetAddress broadcastAddress;
private int port;
private boolean interrupt = false;
  • DatagramSocket reprezentuje socket, pomocou ktorého budeme odosielať datagramový packety.
  • InetAddress obsahuje broadcastovací adresu, na ktoré budeme naše packet odosielať.
  • Premenná interrupt má rovnaký význam, ako v predchádzajúcich kapitolách.

Konštruktor triedy bude neverejný, dostupný len v rámci svojho balíčka a bude prijímať dva parametre typu IParameterFactory a ServerInfoProvider:

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

V konstruktoru najskôr nastavíme názov vlákna, aby sme ho mohli v budúcnosti ľahko rozlíšiť. Potom sa inicializujú inštančný konštanty.

Ďalej vytvoríme privátne metódu, v ktorej budeme inicializovať adresu a socket. Metódu 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);
    }
}

Najskôr sa získa inštancie typu IParameterProvider z továrne pomocou bezparametrické metódy getParameters(). Z parametrov získame hodnotu broadcastovací adresy. Ak hodnota nebude k dispozícii, použijeme predvolenú hodnotu. Do triedy CmdParser si prosím pridajte dva nové atribúty: 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";

Teraz budeme implementovať, prípadne prepisovať metódy, ktoré nám definuje rozhranie IThreadControl, prípadne trieda Thread. Prepíšeme metódu start(), ktorú vybavíme volaním metódy init():

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

Metóda shutdown() bude mať rovnaké telo, ako v mnohých iných prípadoch:

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

Najdôležitejšie metódu run() som si nechal nakoniec:

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čiatku metódy sa zistí, či ak prebehla inicializácia socketu a adresy úspešne. Ak jedna z premenných bude mať hodnotu null, nastaví sa premenná interrupt na hodnotu true, čím sa zabezpečí, že sa vlákno ukončí. V nekonečnej slučke sa získa správa s informáciami o serveri a prevedie sa na balík dát. Tento balík dát sa vloží do datagramu a socketom sa odošle do sveta. Nasleduje uspanie vlákna na hodnotu konštanty SLEEP_TIME. Touto nekonečnú slučkou zaistíme, že sa náš server zviditeľní naprieč celou lokálnou sieťou.

Sme takmer na konci lekcie, ale ešte stihneme vytvoriť továreň. Vytvoríme teda triedu MulticastSenderFactory, ktorá implementuje rozhranie IMulticastSenderFactory. Rozhranie vyžaduje, aby trieda obsahovala jedinú metódu 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);
    }
}

Nakoniec zaregistrujeme továreň v module ServerModule:

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

To by bolo z prvej časti dnešnej lekcie všetko. V druhej časti, Java server - Propagácia lokálnou sieťou (2. časť) , naimplementujeme zvyšok funkcionality na serverovej časti.


 

Predchádzajúci článok
Java server - Systém pluginov
Všetky články v sekcii
Server pre klientskej aplikácie v Jave
Preskočiť článok
(neodporúčame)
Java server - Propagácia lokálnou sieťou (2. časť)
Článok pre vás napísal Petr Štechmüller
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje primárně programování v Javě, ale nebojí se ani webových technologií.
Aktivity