|
||||||||||||||||||||||||||||||||||||||||||||||||
Menu
Distributions (131)
Software (10844)
|
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.
Odeslání datagramuZač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 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
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 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
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í 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 klientSpojově 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
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 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 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
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 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 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ánuKomunikač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.
|
Search Software
Search Google
|
||||||||||||||||||||||||||||||||||||||||||||||
©Pavel Kysilka - 2003-2024 | maillinuxsoft.cz | Design: www.megadesign.cz |