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 47266×
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
|
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 ...
|