Hlavní navigace

Síťování v Javě: Úvod

Martin Majer

První díl seriálu o práci se sítí v Javě. Začneme zlehka teoretickým popisem několika důležitých tříd. Na závěr si naprogramujeme první aplikaci, kterou bude vlastní telnet klient.

V prvním díle tohoto seriálu si povíme, jaké balíky Javy slouží k práci se sítí. Podrobněji se zaměříme na třídy java.net.InetAd­dress, java.net.Inet­SocketAddress a java.net.Socket. Nakonec si ukážeme, jak vytvořit jednoduchého konzolového telnet klienta. Server vytvoříme v následujících článcích, dále budeme programovat vícevláknové aplikace pro více uživatelů. Ukážeme si, jak používat moderní New I/O (NIO) rozhraní Javy a skončíme u IRC chatovacího serveru.

Nepočítejte s žádným vyčerpávajícím výkladem teorie o Java API a implementaci TCP/IP a UDP protkolů. Pokud se budete chtít o dané třídě či rozhraní dozvědět více, nahlédněte do dokumentace k jejímu API. Seriál bude zaměřen výhradně na praktické příklady; teorii nutnou k jejich pochopení ale samozřejmě vypustit nelze.

Balíky

Pracovat budeme s balíky java.net, java.io a java.nio, tak si je nyní ve stručnosti popíšeme. První dva uvedené jsou součástí Java API už od své první verze. Balík java.net nabízí základní funkčnost pro jednodušší síťové aplikace. Říkám jednodušší, protože pro každý nově vytvořený soket je nutné spustit vlákno navíc. V java.io jsou sice umístěny třídy spíše pro práci se soubory, některé z nich jsou však nesmírně užitečné. A to nejen při síťování.

Mladší java.nio umožňuje práci s novým NIO rozhraní, které bylo začleněno ve verzi 1.4. Jeho hlavní předností oproti java.net je schopnost používat pouze jedno vlákno pro neomezené množství soketů (respektive klientů připojených k serveru), čímž se výrazně zvyšuje výkon aplikací. Za ostatní novinky z java.nio uvedu např. zcela nový přístup k souborům nebo možnost soubory uzamknout.

Třídy

java.net.InetAd­dress

Tato třída reprezentuje adresu v síti podle IP protokolu. Zde je vypsáno několik jejích metod:

Důležité metody z java.net.Ine­tAddress
static InetAddress getByAddress(byte[] addr) Vrací adresu na základě pole bajtů. 4 bajty pro IPv4, 16 bajtů pro IPv6.
static InetAddress getByName(String host) Vrací adresu podle hostname.
static InetAddress getLocalHost() Vrací adresu pro localhost.
boolean isReachable(int timeout) Zjistí, jestli se lze připojit k cíli na zadané adrese.

java.net.Inet­SocketAddress

InetSocketAddress je kombinace třídy InetAddress a portu. Tuto třídu budeme využívat k připojování soketů na server.

K vytváření nových instancí budeme nejčastěji volat konstruktory InetSocketAddress(InetAddress addr, int port) nebo InetSocketAddress(String hostname, int port).

java.net.Socket

Sockety jsou jedny z nejdůležitějších částí síťových aplikací, jejich prostřednictvím totiž probíhá veškerá síťová komunikace. V Javě se pro vytvářen soketů používá třída java.net.Socket. Zde uvádím, podobně jako u InetAddress, její nejdůležitější metody:

Důležité metody z java.net.Socket
void connect(Socke­tAddress endpoint) Připojí se k serveru. Při neúspěchu vyhodí výjimku  SocketTimeoutException.
InputStream getInputStream() Vrací vstupní proud soketu, ze kterého čteme příchozí data.
OutputStream getOutputStream() Vrací výstupní proud soketu, do kterého data zapisujeme.
void close() Uzavře soket.

Dokumentace

Více se o těchto třech třídách můžete dozvědět v dokumentaci k balíku java.net na adrese http://java.sun­.com/j2se/1.5­.0/docs/api/ja­va/net/package-summary.html.

Telnet klient

Po únavné teorii ;-) se konečně dostáváme k něčemu mnohem zajímavějšímu – ke slibovanému telnet klientovi. Klient bude běžet ve dvou vláknech. První vlákno bude vyčkávat na vstup z konzole a posílat data soketem na server, druhé bude dělat přesný opak. Čekat na data ze serveru a tisknout.

Vlákno TelnetThread

Všimněte si, že obě vlákna vlastně dělají stejnou věc: čtou z nějakého vstupního proudu a následně zapisují do proudu výstupního. To nám velice ulehčí práci, pro obě vlákna nám totiž stačí pouze jedna společná třída.

Nejdůležitější kód třídy TelnetThread se nachází v metodě run(). Do bufferu o velikosti 64 bytů načítá ze vstupního proudu data, která okamžitě a bez jakékoliv změny pošle na výstup. Ve chvíli, kdy se vstupní proud uzavře, vlákno se samo ukončí.

while(true) {
    int nbytes = is.read(b);
    if(nbytes == -1) break;
    os.write(b, 0, nbytes);
} 

Proměnná is odkazuje na vstup, proměnná os na výstup. Bytový buffer jsme pojmenovali jako b. Také si povšimněte podmínky, ve které zjišťujeme, jestli se počet přečtených bytů nbytes rovná hodnotě –1. Jakmile jakýkoliv vstupní proud vrátí tuto hodnotu, znamená to, že byl ukončen. U vstupního proudu soketu je tato návratová hodnota signálem odpojení.

Připojení soketu k serveru

Podle parametrů z konzole sestavíme adresu typu InetSocketAddress. Dále vytvoříme soket.

InetSocketAddress addr = new InetSocketAddress(hostname, port);
Socket socket = new Socket(); 

Nyní se pokusíme připojit. V případě neúspěchu vyvolá příkaz některou z dceřiných výjimek java.io.IOExcep­tion.

socket.connect(addr); 

Spuštění vláken TelnetThread

Vytvoříme obě dvě vlákna typu TelnetThread a jako parametry jim předáme použité proudy. Vlákno čtoucí z konzole nakonfigurujeme jako démona – nikdy totiž neskončí (vstupní proud konzole se neuzavře). To by ale bránilo ukončení aplikace po odpojení soketu. Na smrt démonových vláken však konec běhu aplikace nečeká, proto ho tak musíme označit. Obě vlákna spustíme, přičemž hlavní vlákno necháme čekat, dokud neskončí TelnetThread, které čte ze soketu.

Thread reading = new TelnetThread(socket.getInputStream(), System.out);
Thread writing = new TelnetThread(System.in, socket.getOutputStream());
writing.setDaemon(true);

reading.start();
writing.start();

reading.join(); 

Nakonec pro jistotu zavoláme metodu close() soketu.

Celý zdrojový kód

Zde je kompletní a detailně okomentovaný zdrojový kód telnetu:

import java.io.*;
import java.net.*;

/** Hlavní třída programu Telnet. Spouští se příkazem "java Telnet host port". */
public class Telnet {

    /** Hlavní metoda. Připojí se k serveru a spustí dvě vlákna TelnetThread. */
    public static void main(String[] args) {
        //ověřit počet parametrů
        if(args.length < 2) {
            System.out.println("Použití: java Telnet host port");
            return;
        }

        String hostname = args[0]; //získat hostname

        //získat port
        int port = 0;
        try {
            port = Integer.parseInt(args[1]); //převést parametr na číslo
        }
        catch(NumberFormatException e) {
            System.out.println("Neplatný port");
            System.exit(-1);
        }

        //vytvořit adresu a soket
        InetSocketAddress addr = new InetSocketAddress(hostname, port);
        Socket socket = new Socket();

        try {
            socket.connect(addr); //pokusit se připojit

            //vlákno, které čte ze soketu a tiskne na konzoli
            Thread reading = new TelnetThread(socket.getInputStream(), System.out);
            //vlákno, které čte z konzole a posílá data do soketu
            Thread writing = new TelnetThread(System.in, socket.getOutputStream());
            writing.setDaemon(true); //vytvořit démona

            //spustit vlákna
            reading.start();
            writing.start();

            reading.join(); //počkat odpojení
            socket.close(); //uzavřit soket
        }

        /* Probuzení hlavního vlákna z čekání. Nemělo by nastat. */
        catch(InterruptedException e) {
            e.printStackTrace();
        }
        /* Vypršel čas připojení k serveru. */
        catch(SocketTimeoutException e) {
            System.out.println("Nelze se připojit k serveru.");
            System.exit(-1);
        }
        /* Neznámý host. */
        catch(UnknownHostException e) {
            System.out.println("Neznámý host.");
            System.exit(-1);
        }
        /* Jiná IO výjimka. Obvykle NoRouteToHostException nebo ConnectException. */
        catch(IOException e) {
            System.out.println("IO chyba:");
            e.printStackTrace();
            System.exit(-1);
        }

    }


    /** Toto vlákno čeká na data ze zadaného vstupního proudu a okamžitě je přeposílá do výstupního proudu. */
    static class TelnetThread extends Thread {

        /** Vstupní proud. */
        private InputStream is;
        /** Výstupní proud. */
        private OutputStream os;

        /** Vytvoří nové vlákno pracující se zadanými proudy. */
        public TelnetThread(InputStream is, OutputStream os) {
            this.is = is;
            this.os = os;
        }

        /** Hlavní metoda vlákna. */
        public void run() {
            byte[] b = new byte[64]; //vytvořit buffer

            try {
                while(true) {
                    int nbytes = is.read(b); //přečíst bajty
                    if(nbytes == -1) break; //ověřit konec proudu
                    os.write(b, 0, nbytes); //zapsat bajty
                }
                System.out.println("Vstupní proud uzavřen.");
            }
            catch(IOException e) {
                System.out.println("IO chyba:");
                e.printStackTrace();
                System.exit(-1);
            }
        }

    }

} 

Spuštění telnetu

Telnet spustíte pomocí příkazu java Telnet host port:

wanto@karmaj:~/root/java_site> java Telnet www.google.com 80
GET / HTTP/1.1
Host: www.google.com

HTTP/1.1 200 OK
... 

Závěr

Dnes jsme si vysvětlili úplné základy programování síťových aplikací v Javě. Popsali jsme si tři důležité třídy a naprogramovali naší první klientskou aplikaci – telnet! Příště si ukážeme, jak vytvořit jednoduchý server – to abychom mohli náš telnet nějak využít :-).

STB2

Velká velikonoční soutěž o set-top-boxy

Zajímá vás digitální vysílání? Chcete sledovat televizní programy v digitální kvalitě? Pak se zúčastněte Velké velikonoční soutěže společnosti Internet Info, vydavatele serveru DigiZone.cz, a vyhrajte jeden z deseti set-top-boxů. Kvůli velikonoční výslužce už není nutné mlátit holky! Stačí správně odpovědět na soutěžní otázky a počkat na slosování výherců. Pokud nevyhrajete, nezoufejte. DigiZone.cz spustil internetový obchod se set-top-boxy, kde si určitě vyberete ten správný přijímač.
Našli jste v článku chybu?