LINUXSOFT.cz Přeskoč levou lištu

ARCHIV



   

> Java (19) - síťová komunikace II.

Různé druhy komunikací na internetovém protokolu - to bude téma tohoto článku. Budeme komunikovat protokoly UDP a TCP, podíváme se také na vyšší vrstvy komunikačního modelu.

12.10.2005 07:00 | Lukáš Jelínek | Články autora | přečteno 47113×

Odeslání datagramu

Začneme tím nejjednodušším. Potřebujeme poslat samostatný paket (datagram) nějakému příjemci, jehož adresu (jmennou nebo číselnou) a port známe. Bude to, jak je pro protokol UDP typické, nezabezpečený přenos dat - datagram se může poškodit, zcela ztratit, může přijít několikrát, pořadí příchodu datagramů k příjemci se může lišit od pořadí odeslání. Veškerá režie proto leží na uživatelské aplikaci (popis však přesahuje prostorové možnosti tohoto článku).

Zkusme si tedy vytvořit datagram, který příjemci pošleme. V nám již důvěrně známém balíku java.net je třída DatagramPacket, která představuje v podstatě pole bajtů připravené k odeslání. Instance tohoto objektu obsahuje také cílovou adresu a číslo portu (nemusí být nastaveny). Jak vytvořit takový datagram, napoví příklad:

String s = "testovací datagram";

byte ba[];

try {
    ba = s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
  ...
}

DatagramPacket dgp1 = new DatagramPacket(ba, ba.length);
DatagramPacket dgp2 = new DatagramPacket(ba, 2, 3);

DatagramPacket dgp3 = null;
DatagramPacket dgp4 = null;

try {
    dgp3 = new DatagramPacket(ba, ba.length,
            InetAddress.getByName("pocitac.domena.cz"), 12345);
    dgp4 = new DatagramPacket(ba, ba.length,
            new InetSocketAddress("pocitac.domena.cz", 12345));
} catch (Exception e) {
    System.err.println(e.getMessage());
}

První dva uvedené pakety se vytvoří bez přiřazené adresy a portu. Jeden pojme celý textový řetězec (protože se, jak je zřejmé, datagram vytvoří z celého pole), zatímco druhý se vytvoří od druhého bajtu s délkou 3 bajty. Pozor ovšem, takový postup je u textových dat (které jsem zde použil pro názornost) nepříliš vhodný, protože zejména pro kódování UTF-8 obvykle předem nevíme, jak se znaky zakódují - proto je lepší znaky vybrat předem. Pro obecná (binární) data ovšem můžeme klidně s úsekem pole pracovat.

Druhé dva pakety obsahují adresu a port - v obou případech stejné, liší se pouze způsob vytvoření. Všimněte si, že je potřeba zachytávat výjimky, případně je předávat výš. Abych byl přesný, výjimky vyhazuje pouze konstruktor přijímající objekt typu SocketAddress, u InetAddress se zde jedná o ošetření výjimky z metody getByName().

Máme datagram, ale co s ním? Poslat, samozřejmě. Na to ale potřebujeme prostředek, který to zajistí. Tím prostředkem je UDP socket, v Javě reprezentovaný třídou DatagramSocket. Socket obsadí určitý port na některé z adres počítače (viz minulý díl - rozhraní a adresy). Adresu a port si můžeme zvolit, ale také si je můžeme nechat přidělit automaticky. Můžeme také socket "připojit" na adresu příjemce - odesílat pakety pak bude možné jen tomuto příjemci (totéž se týká příjmu). Datagram odešleme zavoláním metody send(). Viz příklad:

try {
    DatagramSocket sock1 = new DatagramSocket();
    
    sock1.send(dgp1);  // chyba - datagram ani socket nemá cíl. adresu a port
    sock1.send(dgp3);  // funguje
    
    DatagramSocket sock2 = new DatagramSocket(55555);
    sock2.connect(InetAddress.getByName("pocitac.domena.org"), 44444);
    
    sock2.send(dgp2);  // funguje
    sock2.send(dgp4);  // chyba - výjimka kvůli neshodě adres
} catch (Exception e) {
    e.printStackTrace();
}

Příklad využívá datagramy vytvořené výše (v reálném programu se ale samozřejmě musí zajistit, aby pakety skutečně existovaly). Nyní tedy vytvoříme dva sockety - první z nich bude mít adresu a port přidělené automaticky (což v našem případě nehraje roli) a nebude omezen na konkrétní cílovou adresu. Z toho vyplývá, že první datagram by odmítl odeslat (chybí mu cílová adresa a port), druhý by odeslal bez problémů.

Druhý socket má ručně zvolený místní port 55555 a je pevně nastaven na komunikaci s určenou adresou a portem. Proto první paket odešle, zatímco druhý nikoli, protože se cílová adresa a port v datagramu liší od cílové adresy a portu socketu. Všechny chybové stavy se hlásí výjimkami - ovšem pozor, některé stavy závisí na bezchybné funkci ICMP komunikace (kterou mohou někteří příliš horliví správci sítí blokovat), a ani pak nevyhození výjimky automaticky neznamená úspěch.

Na druhém konci

Datagram tedy úspěšně odešel z našeho programu (a pokud nemá lokální adresu příjemce, pak i z počítače). Na druhém konci by na něj měl někdo čekat - nemusí, ale pak samozřejmě posílání dat nemá smysl. Vytvoříme tedy úplně stejný socket jako při posílání datagramu. Paket se přijímá metodou receive(). Toto volání vždy blokuje do přijetí paketu nebo vypršení časového limitu (v tom případě ovšem končí vyhozením výjimky SocketTimeoutException). Následující příklad ukazuje implementaci "UDP echo" - program čeká na data, a když přijdou, pošle je zpět odesílateli.

try {
    DatagramSocket sock = new DatagramSocket(20000);
    DatagramPacket dgp = new DatagramPacket(new byte[1000], 1000);
    
    sock.setSoTimeout(600000);  // timeout 10 minut
    
    while (true) {
        sock.receive(dgp);
        sock.send(dgp);
    }
    
} catch (Exception e) {
    ...
}

Je to opravdu velmi jednoduché. Vytvoříme socket na portu 20000, připravíme datagram s bufferem pro 1000 bajtů, a nastavíme socketu časový limit 10 minut. To bude také jediný způsob ukončení (skrze výjimku) tohoto primitivního prográmku, pokud nepočítám stisknutí Ctrl-C a podobné způsoby. Metoda receive() pracuje poněkud "nejavovským" způsobem - předáváme jí již vytvořený objekt, který metoda pouze naplní. Zde je nutno zdůraznit, že buffer datagramu musí být dostatečně velký, aby se do něj data vešla (jinak se oříznou). Platnou délku metoda zapíše do datagramu, odkud ji zjistíme zavoláním getLength(). Velikost bufferu volíme podle velikosti očekávaných dat, nemá smysl vytvářet zbytečně velké buffery pro data o několika bajtech.

Pozor - použití čísel portů podléhá omezením práv na konkrétním systému. Obvykle lze bez omezení používat porty od 1024, nižší vyžadují administrátorská práva.

TCP klient

Spojově orientovaná komunikace je v Javě ještě jednodušší než ta paketová. Pracuje se totiž se streamy - úplně stejně, jako při práci se soubory. Dá se říci, že díky této abstrakci se nemusí program vůbec zabývat, jak je komunikace řešena. Prostě získá příslušný stream a hotovo. Pro sestavení spoje se používá opět socket. Vše je vidět na následujícím příkladu:

try {
    Socket sock1 = new Socket();            // vytvoření socketu
    sock1.connect("www.linuxsoft.cz", 80);  // připojení
    sock1.close();                          // zavření socketu

    Socket sock2 = new Socket("www.linuxsoft.cz", 80); // hned se připojíme
    
    BufferedReader br = new BufferedReader(
            new InputStreamReader(sock2.getInputStream()));
    BufferedWriter bw = new BufferedWriter(
            new OutputStreamWriter(sock2.getOutputStream()));
    
    bw.write(request);  // zapíšeme předem připravený požadavek
    bw.flush();         // odeslání z bufferu
    
    String line = "";
    
    // dokud jsou data, opakuj
    while (line != null) {
        line = br.readLine();
        if (line != null)
            System.out.println(line);  // platná data vypisuj
    }
    
    sock2.close(); // zavření socketu
    
} catch (Exception e) {
    ...
}

Tento kus kódu představuje velmi primitivního HTTP klienta. Nejprve k vytvoření socketu - v prvním případě vytvoříme nepřipojený a připojíme ho teprve ve druhém kroku. Po použití (resp. zde bez použití) se socket zavře, čímž se zruší (korektním způsobem) TCP spoj mezi počítači.

Ve druhém případě již socket využijeme. Zde se vytváří s okamžitým připojením. Ze socketu získáme streamy, ty jsou napojeny na další tak, že pak máme k dispozici textové bufferované streamy. Do toho výstupního zapíšeme předem připravený (není součástí příkladu) HTTP požadavek, a ze vstupního streamu můžeme číst data. Ta se v tomto případě vypisují na standardní výstup. Po použití socket opět zavřeme.

Parametry socketů (timeouty, přenosové třídy, buffery apod.) můžeme různě nastavovat - je samozřejmě potřeba vědět, k čemu nám změna výchozího nastavení bude.

Jednoduchý server

Typické chování serveru je, že čeká na připojení klienta, a až ten se připojí, obslouží ho - a současně čeká na připojení případných dalších klientů. V Javě to vypadá tak, že si vytvoříme instanci třídy ServerSocket a zde metodou accept() nasloucháme na příslušném portu. V okamžiku, kdy se připojí klient, metoda vrátí instanci třídy Socket. Z ní, úplně stejně jako na klientské straně, získáme vstupní a výstupní stream, a přes tyto streamy již normálně komunikujeme.

Zbývá ještě vyřešit, jak během obsluhy klienta zajistit čekání na další klienty. Java je výrazně multithreadově orientovaná - a z toho vyplývá i řešení tohoto problému. Prostě vytvoříme nové vlákno, které obsluhuje klienta, zatímco původní vlákno opět zavolá metodu accept() a čeká na další připojení. Ještě se nelze nezmínit, že počet klientů čekajících na obsloužení je omezený. Výchozí hodnota je 50, v konstruktoru lze určit jinou - ovšem s rozumem, protože je lepší klienta odmítnout, než obsluhovat neúnosně pomalu.

boolean quit = false;
    
try {
    ServerSocket ss = new ServerSocket(22222);

    while (!quit) {
        final Socket sock = ss.accept();
        Thread t = new Thread() {
            public void run() {
                try {
                    InputStream is = sock.getInputStream();
                    OutputStream os = sock.getOutputStream();
                     
                    ...
                    
                    sock.close();
                } catch (IOException e) {
                    ...
                }
            }
        };
        t.setDaemon(true);
        t.start();
    }
} catch (Exception e) {
    ...
}

Příklad ukazuje řešení pomocí anonymní třídy. Na místě použití se předefinuje metoda run() třídy Thread, vlákno se nastaví jako démon (tzn. jeho běh není překážkou ukončení programu) a spustí se. Všimněte si, že je proměnná sock deklarována s modifikátorem final - to je nutné, protože se s ní pracuje v metodě vytvořené anonymní třídy. Program běží, dokud je (v okamžiku připojení klienta) proměnná quit nastavena na false (odkud se hodnota změní, teď není důležité); poslední klient již samozřejmě není obsloužen (a obsluhování jiných klientů může být také přerušeno). Není to úplně nejlepší, ale vhodné řešení ukončování serverů je poněkud složitější - budeme se jím zabývat někdy později.

HTTP klient

Sliboval jsem vyšší vrstvy komunikace, zde je jedna z nich. Protokol HTTP je jeden z nejpoužívanějších, proto mám v Javě k dispozici snadno použitelnou implementaci HTTP klienta. Je to třída HttpURLConnection (jejím potomkem je velice podobná třída HttpsURLConnection pro připojení přes SSL).

HttpURLConnection je abstraktní třída, její instance tedy nemůžeme vytvářet. Vzhledem k charakteru HTTP spojení na tom není nic překvapivého. Instanci získáme např. metodou openConnection() na instanci třídy URL - tato metoda však vrátí instanci URLConnection, proto si ji (po potřebné kontrole) musíme přetypovat. Důležitou vlastností HttpURLConnection je sdílení prostředků a jejich efektivní správa. Proto je výhodné tento mechanismus používat. Další výhodou je opět abstraktní přístup, kdy stačí mít URL a o nízkoúrovňové operace se nemusíme starat.

try {
    URL url = new URL("http://www.linuxsoft.cz/");    
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    
    System.out.println("Response code: " + con.getResponseCode());
    System.out.println("Content type: " + con.getContentType());
    
    BufferedReader br = new BufferedReader(
            new InputStreamReader(con.getInputStream()));
    
    String s = "";
    while (s != null) {
        s = br.readLine();
        if (s != null)
            System.out.println(s);
    }
    
    con.disconnect();
} catch (Exception e) {
    ...
}

Příklad ukazuje jednoduché získání dokumentu z HTTP serveru. Na základě URL vytvoříme spojení na server, příslušná instance třídy HttpURLConnection sama odešle požadavek (při přetypování v příkladu se mlčky předpokládá, že je objekt této třídy skutečně vrácen - pokud by to tak nebylo, skončí to výjimkou ClassCastException). Příklad vypíše kód odpovědi serveru a MIME typ dat. Pak se získá vstupní stream a data z něj se vypisují na standardní výstup. To je jedna z cest, jak s daty naložit. Existují i další cesty, ale ty si necháme na jindy.

Využití proxy serveru

V některých případech není k dispozici přímé připojení do Internetu nebo jiné sítě. Jediné spojení zajišťuje proxy server. Je to sice nepříjemné, nicméně řešitelné, a to poměrně snadno, zejména od Javy 5.0. Tam je totiž přímo třída Proxy, jejíž instanci předáme metodě openConnection(). Celé je to poněkud rozsáhlejší, ale v tomto okamžiku stačí vědět, že proxy serverů je několik typů, z nichž zde vybereme HTTP (ještě se může hodit DIRECT, což značí přímé spojení bez proxy). Opět bude nejlepší ukázat si to na příkladu:

try {
    URL url = new URL("http://www.linuxsoft.cz/");
    Proxy p = new Proxy(Proxy.Type.HTTP,
            new InetSocketAddress("10.0.0.200", 3128));
    HttpURLConnection con = (HttpURLConnection) url.openConnection(p);
    
    ...
    
} catch (Exception e) {
    ...
}

Je to úplně stejné jako v předchozím příkladu - s tím rozdílem, že použijeme proxy server. Ten běží na počítači s adresou 10.0.0.200 na (obvyklém) portu 3128. Pokud proxy server pracuje, jak má, nebude se chování od předchozího příkladu lišit (pouze bychom měli přítomny HTTP hlavičky specifické pro tento druh připojení).

Bez vláken ani ránu

Komunikační činnosti nyní na chvíli opustíme (vrátíme se k nim ještě později), a přejdeme k tomu, bez čeho se například při síťové komunikaci prakticky nelze obejít - k vláknům a práci s nimi. Vlákna se již několikrát objevila v příkladech, ale byla to vždy jen malá ukázka toho, co se s nimi dá dělat.

Verze pro tisk

pridej.cz

 

DISKUZE

IP 21.12.2005 22:33 Jaro Drencak
chybička + request 27.10.2008 14:52 Miroslav Kalina




Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

28.11.2018 23:56 /František Kučera
Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1

12.11.2018 21:28 /Redakce Linuxsoft.cz
22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář

6.11.2018 2:04 /František Kučera
Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

4.10.2018 21:30 /Ondřej Čečák
LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář

18.9.2018 23:30 /František Kučera
Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

9.9.2018 14:15 /Redakce Linuxsoft.cz
20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business. Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář

12.8.2018 16:58 /František Kučera
Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář

16.7.2018 1:05 /František Kučera
Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

31.7.2023 14:13 / Linda Graham
iPhone Services

30.11.2022 9:32 / Kyle McDermott
Hosting download unavailable

13.12.2018 10:57 / Jan Mareš
Re: zavináč

2.12.2018 23:56 / František Kučera
Sraz

5.10.2018 17:12 / Jakub Kuljovsky
Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2024) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze