Java týden Předvánoční slevová akce
Využij předvánočních slev a získej od nás 20 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 4 - Java server - Vlákno serveru

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Java server - Google Guice, jsme vložili správu závislostí do rukou knihovny Google guice. Dnes si vytvoříme základní kostru vlákna našeho Java serveru.

Funkce vlákna

Toto vlákno bude sloužit jako hlavní přístupový bod. Zde se bude navazovat spojení s klienty.

Vytvoříme si nový balíček core, do kterého budeme vkládat veškerou důležitou funkčnost serveru.

Továrna vlákna

Začneme podobně, jako v minulé lekci. Vytvoříme továrnu, která nám vytvoří instanci vlákna. V balíčku core vytvoříme nový balíček server, ve kterém vytvoříme následující třídy a rozhraní:

  • IServerThread - rozhraní poskytující metody pro komunikaci s vláknem serveru
  • IServerThreadFactory - rozhraní obsahující metodu pro vytvoření instance rozhraní IServerThread
  • ServerThread - implementace rozhraní IServerThread
  • ServerThreadFactory - implementace rozhraní IServerThreadFactory

Pouze toto rozhraní vložíme přímo do balíčku core:

  • IThreadControl - pomocné rozhraní pro práci s vlákny

Nejdříve zadefinujeme metody pro rozhraní IThreadControl. Toto rozhraní bude obsahovat dvě metody:

  • start(), která spustí vlákno
  • shutdown(), pomocí které budeme vlákno informovat, že má zahájit ukončovací sekvenci

Signatura metod bude následující:

void start();
void shutdown();

Rozhraní IServerThread necháme dědit z rozhraní IThreadControl.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Rozhraní IServerThreadFactory bude obsahovat jednu tovární metodu pro výrobu instance třídy IServerThread:

IServerThread getServerThread(IParameterProvider parameters) throws IOException;

Implementace rozhraní

Rozhraní jsme si nadefinovali, tak je pojďme implementovat. Začneme třídou ServerThread, která implementuje rozhraní IServerThread. Třídu ještě upravíme tak, že ji necháme dědit od třídy Thread. Definice třídy bude tedy vypadat takto:

class ServerThread extends Thread implements IServerThread

Ve třídě si nadefinujeme jednu třídní konstantu:

private static final int SOCKET_TIMEOUT = 5000;

dále jednu instanční konstantu:

private final int port;

a jednu instanční proměnnou:

private boolean running = false;

která bude indikovat, zda-li má vlákno běžet, nebo se ukončit.

Dále vytvoříme konstruktor, který přijímá jediný parametr: int port. V konstruktoru nastavíme název vlákna na "ServerThread" a inicializujeme instanční konstantu port:

ServerThread(int port) throws IOException {
    super("ServerThread");
    this.port = port;
}

Nakonec implementujeme metody, které nám definuje rozhraní, tedy:

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

@Override
public void run() {
    try (ServerSocket serverSocket = new ServerSocket(port)) {
        serverSocket.setSoTimeout(SOCKET_TIMEOUT);
        while (running) {
            try {
                final Socket socket = serverSocket.accept();
            } catch (SocketTimeoutException ignored) {}
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

V metodě shutdown() nastavíme proměnné running hodnotu false a zavoláme join(), abychom počkali na ukončení vlákna. Metoda run() vytvoří novou instanci třídy ServerSocket a nastaví timeout na hodnotu konstanty SOCKET_TIMEOUT, tedy 5 vteřin. Tím zajistíme, že se každých 5 vteřin vyvolá vyjímka SocketTimeoutException a zkontroluje se proměnná running. Následuje volání metody accept() nad instancí ServerSocketu. Toto volání je blokující, což znamená, že vlákno nebude vykonávat žádnou činnost, dokud se nepřipojí klient, nebo se nevyvolá výjimka. Zpracování nově navázaného spojení necháme na další lekci. Tím bychom byli pro tuto chvíli s třídou ServerThread hotoví a můžeme se pustit do implementace továrny.

Implementace továrny

Třídě ServerThreadFactory přidáme anotaci @Singleton z knihovny Google guice. Tato anotace nám zajistí, že kdykoliv v budoucnu budeme žádat o továrnu, dostaneme jednu a tu samou instanci. Třída ServerThreadFactory musí implementovat jedinou metodu getServerThread(). Metoda přijímá jako parametr rozhraní IParameterProvider, které poskytuje parametry z příkazové řádky. V továrně si zadefinujeme výchozí hodnoty parametrů, které se použijí v případě, že bychom nějaký parametr nepředali při spuštění serveru:

// Výchozí hodnota portu
private static final int DEFAULT_SERVER_PORT = 15378;
// Výchozí maximální počet klientů
private static final int DEFAULT_MAX_CLIENTS = 3;
// Výchozí velikost čekací fronty
private static final int DEFAULT_WAITING_QUEUE_SIZE = 1;

Nyní můžeme vyplnit tělo metody getServerThread():

@Override
public IServerThread getServerThread(IParameterProvider parameters) throws IOException {
    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(port);
}

V metodě získáme jednotlivé parametry a nakonec vytvoříme a vrátíme novou instanci třídy ServerThread. V budoucnu využijeme i zbylé parametry.

Po implementaci všech rozhraní konkrétními třídami můžeme zaregistrovat továrnu na vlákno serveru ve třídě ServerModule. Registrace bude stejná jako v případě továrny na parametry:

bind(IServerThreadFactory.class).to(ServerThreadFactory.class);

Nakonec se přesuneme do třídy Server, kde vše zadrátujeme dohromady. Nejdříve přidáme další instanční konstantu typu IServerThreadFactory:

private final IServerThreadFactory serverThreadFactory;

a také přidáme stejný parametr do konstruktoru, kde továrnu inicializujeme:

@Inject
public Server(IParameterFactory parameterFactory, IServerThreadFactory serverThreadFactory) {
    this.parameterFactory = parameterFactory;
    this.serverThreadFactory = serverThreadFactory;
}

Nyní upravíme metodu run():

private void run(String[]args) throws IOException {
    final IParameterProvider parameters = parameterFactory.getParameters(args);
    final IServerThread serverThread = serverThreadFactory.getServerThread(parameters);
    serverThread.start();
    while(true) {
        final String input = scanner.nextLine();
        if ("exit".equals(input)) {
            break;
        }
    }
    serverThread.shutdown();
}

V metodě nejdříve získáme parametry které předáme továrně na vlákno serveru, která nám vrátí instanci třídy IServerThread. Metodou start() spustíme vlákno serveru. Vlákno se spustí, ale protože jsme nenadefinovali žádnou činnost, okamžitě se ukončí. Následuje nekonečná smyčka, která očekává vstup od uživatele, dokud uživatel nezadá slovo "exit". Když uživatel zadá "exit", začne se server ukončovat. Metodou shutdown() informuje vlákno serveru, že má začít ukončovat svůj běh.

To by bylo z dnešní lekce vše. Příště, v lekci Java server - Správce spojení, vytvoříme třídu, která bude zpracovávat příchozí klienty.


 

Stáhnout

Staženo 16x (117.35 kB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

 

Článek pro vás napsal Petr Štechmüller
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se věnuje primárně programování v Jave, ale nebojí se ani webových technologií.
Předchozí článek
Java server - Google Guice
Všechny články v sekci
Server pro klientské aplikace v Javě
Miniatura
Následující článek
Java server - Správce spojení
Aktivity (2)

 

 

Komentáře

Avatar
Jenkings
Redaktor
Avatar
Jenkings:30. června 8:30

Nějak se mi nedaří donutit ten server ke čtení vstupů. Kontroloval jsem si to ještě podle stažených zdrojáků a mám vše v podstatě stejně, ale při spuštění mi pokaždé vyhodí chybu:

Exception in thread "main" java.util.NoSuchE­lementExcepti­on: No line found
at java.util.Scan­ner.nextLine(Scan­ner.java:1540)
at Server.run(Ser­ver.java:34)
at Server.main(Ser­ver.java:25)

Zkoušel jsem ještě to ošetřit obalením podmínkou scanner.hasNext(), pak aplikace zůstane běžet a čeká na vstup, ale při debugování i při nějakém vstupním textu nikdy podmínka neprojde.

Odpovědět
30. června 8:30
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Jenkings
Petr Štechmüller:30. června 8:34

Ahoj, nevoláš někde před čtením

scanner.close()

?

Tato chyba se právě typicky vyskytuje, když načítáš řádky z uzavřeného scanneru.

Odpovědět
30. června 8:34
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Jenkings
Redaktor
Avatar
Odpovídá na Petr Štechmüller
Jenkings:30. června 8:46

Tuhle fci nikde nevolám. tady je můj kód:

https://www.itnetwork.cz/…lighter/1196

Odpovědět
30. června 8:46
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Jenkings
Petr Štechmüller:30. června 9:20

Hm, zajímavé :-?
A když spustíš můj kód, tak ti to funguje?

Odpovědět
30. června 9:20
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Jenkings
Redaktor
Avatar
Odpovídá na Petr Štechmüller
Jenkings:30. června 9:35

Taky na to koukám už delší dobu a zkusil jsem všechno možné, ale nějak jsem nezjistil příčinu. Celý tvůj kód jsem ještě kompilovat nezkoušel, ale můžu to zkusit. Teď nevím kdy to stihnu, určitě se ozvu hned jak to zkusím

Odpovědět
30. června 9:35
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Jenkings
Redaktor
Avatar
Odpovídá na Petr Štechmüller
Jenkings:30. června 9:59

Tak při kompilaci úplně stejný problém :(

Odpovědět
30. června 9:59
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Jenkings
Petr Štechmüller:30. června 10:02

Ou, tak já se na to podívám na počítači taky...

Odpovědět
30. června 10:02
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 7 zpráv z 7.