Programování

Jednoduchá manipulace s časovými limity sítě

Mnoho programátorů se obává myšlenky na vypršení časového limitu sítě. Běžně se obáváme, že jednoduchý síťový klient s jedním vláknem bez podpory časového limitu se nafoukne do složité noční můry s více vlákny se samostatnými vlákny potřebnými k detekci časového limitu sítě a nějaká forma procesu oznámení mezi blokovaným vláknem a hlavní aplikací. I když je to jedna možnost pro vývojáře, není to jediná. Řešení časového limitu sítě nemusí být obtížný úkol a v mnoha případech se můžete zcela vyhnout psaní kódu pro další vlákna.

Při práci se síťovými připojeními nebo jakýmkoli typem I / O zařízení existují dvě klasifikace operací:

  • Blokovací operace: Stánky pro čtení nebo zápis, operace čeká, dokud nebude připraveno I / O zařízení
  • Neblokující operace: Je proveden pokus o čtení nebo zápis, operace se přeruší, pokud není I / O zařízení připraveno

Síť Java je ve výchozím nastavení formou blokování I / O. Když tedy síťová aplikace Java čte ze soketového připojení, bude obecně čekat neurčitě, pokud nebude okamžitě reagovat. Pokud nejsou k dispozici žádná data, program bude čekat a nebude možné pokračovat v žádné další práci. Jedním z řešení, které problém vyřeší, ale přináší trochu větší složitost, je nechat operaci provést druhým vláknem; tímto způsobem, pokud dojde k zablokování druhého vlákna, aplikace může stále reagovat na příkazy uživatelů nebo v případě potřeby dokonce ukončit pozastavené vlákno.

Toto řešení se často používá, ale existuje mnohem jednodušší alternativa. Java také podporuje neblokující síťové I / O, které lze aktivovat na jakémkoli Zásuvka, ServerSocketnebo DatagramSocket. Je možné určit maximální dobu, po kterou se operace čtení nebo zápisu zastaví před vrácením ovládacího prvku zpět do aplikace. Pro síťové klienty je to nejjednodušší řešení a nabízí jednodušší a lépe spravovatelný kód.

Jedinou nevýhodou neblokujících síťových I / O v prostředí Java je to, že vyžaduje existující soket. Zatímco je tedy tato metoda ideální pro normální operace čtení nebo zápisu, operace připojení se může zastavit po mnohem delší dobu, protože neexistuje žádná metoda pro určení časového limitu pro operace připojení. Mnoho aplikací vyžaduje tuto schopnost; můžete se však snadno vyhnout práci navíc při psaní dalšího kódu. Napsal jsem malou třídu, která vám umožňuje určit hodnotu časového limitu pro připojení. Používá druhé vlákno, ale interní podrobnosti jsou odebrány. Tento přístup funguje dobře, protože poskytuje neblokující I / O rozhraní a podrobnosti druhého vlákna jsou skryty.

Neblokující síťové I / O

Nejjednodušší způsob, jak něco udělat, se často ukáže jako nejlepší. I když je někdy nutné použít vlákna a blokování I / O, ve většině případů se neblokující I / O nabízí mnohem jasnější a elegantnější řešení. Pouze s několika řádky kódu můžete začlenit podporu časového limitu pro jakoukoli aplikaci soketu. Nevěř mi? Číst dál.

Když byla vydána Java 1.1, obsahovala změny API v java.net balíček, který programátorům umožnil specifikovat možnosti soketu. Tyto možnosti dávají programátorům větší kontrolu nad soketovou komunikací. Jedna možnost zejména SO_TIMEOUT, je velmi užitečné, protože umožňuje programátorům určit dobu, po kterou bude operace čtení blokována. Můžeme určit krátké zpoždění nebo vůbec žádné a náš síťový kód odblokovat.

Pojďme se podívat na to, jak to funguje. Nová metoda, setSoTimeout (int) byl přidán do následujících tříd soketů:

  • java.net.Socket
  • java.net.DatagramSocket
  • java.net.ServerSocket

Tato metoda nám umožňuje určit maximální délku časového limitu v milisekundách, kterou budou blokovat následující síťové operace:

  • ServerSocket.accept ()
  • SocketInputStream.read ()
  • DatagramSocket.receive ()

Kdykoli se volá jedna z těchto metod, hodiny začnou tikat. Pokud operace není blokována, resetuje se a restartuje se pouze po opětovném volání jedné z těchto metod; v důsledku toho nikdy nemůže dojít k žádnému vypršení časového limitu, pokud neprovedete síťovou I / O operaci. Následující příklad ukazuje, jak snadné může být vypršení časového limitu bez použití více podprocesů provedení:

// Vytvořte soket datagramu na portu 2000 a naslouchejte příchozím paketům UDP DatagramSocket dgramSocket = nový DatagramSocket (2000); // Zakázat blokování I / O operací zadáním pětisekundového časového limitu dgramSocket.setSoTimeout (5000); 

Přiřazení hodnoty časového limitu zabrání blokování našich síťových operací na neurčito. V tomto okamžiku jste pravděpodobně zvědaví, co se stane, když vyprší časový limit provozu sítě. Spíše než vrácení chybového kódu, který nemusí vývojáři vždy zkontrolovat, a java.io.InterruptedIOException je hozen. Zpracování výjimek je vynikající způsob řešení chybových podmínek a umožňuje nám oddělit náš normální kód od našeho kódu pro zpracování chyb. Kromě toho, kdo nábožensky kontroluje každou návratovou hodnotu s ohledem na nulovou referenci? Vyvoláním výjimky jsou vývojáři nuceni poskytnout obslužnou rutinu úlovku pro časové limity.

Následující fragment kódu ukazuje, jak zpracovat operaci časového limitu při čtení ze zásuvky TCP:

// Nastavit časový limit zásuvky na deset sekund connection.setSoTimeout (10 000); zkuste {// Vytvořit DataInputStream pro čtení ze zásuvky DataInputStream din = new DataInputStream (connection.getInputStream ()); // Číst data až do konce dat pro (;;) {Řetězec line = din.readLine (); if (line! = null) System.out.println (line); jinak zlomit; }} // Výjimka vyvolána, když dojde k vypršení časového limitu sítě catch (InterruptedIOException iioe) {System.err.println ("Časový limit vzdáleného hostitele vypršel během operace čtení"); } // Výjimka vyvolána, když dojde k obecné chybě sítě I / O catch (IOException ioe) {System.err.println ("Chyba sítě I / O -" + ioe); } 

Pouze s několika řádky kódu navíc pro a Snaž se {} catch block, je extrémně snadné zachytit timeouty sítě. Aplikace pak může reagovat na situaci, aniž by se zastavila. Mohlo by to například začít oznámením uživateli nebo pokusem o navázání nového připojení. Při použití soketů datagramu, které odesílají pakety informací, aniž by zaručovaly doručení, mohla aplikace reagovat na časový limit sítě opětovným odesláním paketu, který byl při přenosu ztracen. Implementace této podpory časového limitu trvá velmi málo času a vede k velmi čistému řešení. Jediným okamžikem, kdy neblokující I / O není optimálním řešením, je situace, kdy také potřebujete zjistit časové limity pro operace připojení, nebo když vaše cílové prostředí nepodporuje prostředí Java 1.1.

Zpracování časového limitu při operacích připojení

Pokud je vaším cílem dosáhnout úplné detekce a zpracování časového limitu, budete muset zvážit operace připojení. Při vytváření instance java.net.Socket, je proveden pokus o navázání spojení. Pokud je hostitelský počítač aktivní, ale na portu uvedeném v souboru není spuštěna žádná služba java.net.Socket konstruktor, a ConnectionException bude vyvolána a ovládací prvek se vrátí do aplikace. Pokud je však počítač vypnutý nebo pokud k tomuto hostiteli neexistuje žádná cesta, připojení soketu nakonec samo o sobě vyprší mnohem později. Mezitím vaše aplikace zůstane zmrazená a neexistuje způsob, jak změnit hodnotu časového limitu.

Ačkoli volání konstruktoru soketu se nakonec vrátí, zavádí značné zpoždění. Jedním ze způsobů řešení tohoto problému je použití druhého vlákna, které provede potenciálně blokující připojení, a nepřetržité dotazování tohoto vlákna, aby se zjistilo, zda bylo navázáno připojení.

To však nemusí vždy vést k elegantnímu řešení. Ano, své síťové klienty můžete převést na vícevláknové aplikace, ale množství práce navíc, které je k tomu zapotřebí, je často neúměrné. Díky němu je kód složitější a při psaní pouze jednoduché síťové aplikace je obtížné ospravedlnit potřebné množství úsilí. Pokud píšete spoustu síťových aplikací, zjistíte, že často objevujete kolo znovu. Existuje však jednodušší řešení.

Napsal jsem jednoduchou, opakovaně použitelnou třídu, kterou můžete použít ve svých vlastních aplikacích. Třída generuje připojení TCP soketu bez pozastavení po dlouhou dobu. Jednoduše zavoláte a getSocket metoda, určující název hostitele, port a prodlevu časového limitu, a přijmout soket. Následující příklad ukazuje požadavek na připojení:

// Připojte se ke vzdálenému serveru podle názvu hostitele s časovým limitem čtyři sekundy Socket connection = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Pokud vše půjde dobře, bude soket vrácen, stejně jako standard java.net.Socket konstruktéři. Pokud připojení nelze navázat před zadaným časovým limitem, metoda se zastaví a vyvolá java.io.InterruptedIOException, stejně jako jiné operace čtení soketu, pokud by byl zadán časový limit pomocí a setSoTimeout metoda. Docela snadné, hm?

Zapouzdření síťového kódu s více vlákny do jedné třídy

Zatímco TimedSocket třída je sama o sobě užitečnou součástí, je to také velmi dobrá učební pomůcka pro pochopení toho, jak se vypořádat s blokováním I / O. Když se provede operace blokování, aplikace s jedním vláknem bude blokována na neurčito. Pokud je použito více podprocesů provádění, je nutné zastavit pouze jedno vlákno; druhé vlákno může pokračovat v provádění. Pojďme se podívat na to, jak TimedSocket třídní práce.

Když se aplikace potřebuje připojit ke vzdálenému serveru, vyvolá TimedSocket.getSocket () metoda a předá podrobnosti o vzdáleném hostiteli a portu. The getSocket () metoda je přetížená, což umožňuje a Tětiva název hostitele a InetAddress bude upřesněno. Tento rozsah parametrů by měl být dostatečný pro většinu operací soketů, ačkoli pro speciální implementace lze přidat vlastní přetížení. Uvnitř getSocket () metoda je vytvořeno druhé vlákno.

Pomyslně pojmenovaný SocketThread vytvoří instanci java.net.Socket, což může potenciálně blokovat po značnou dobu. Poskytuje přístupové metody k určení, zda bylo navázáno připojení nebo zda došlo k chybě (například pokud java.net.SocketException byl vyhozen během připojení).

Během navazování připojení primární vlákno čeká na navázání připojení, na výskyt chyby nebo na časový limit sítě. Každých sto milisekund se kontroluje, zda druhé vlákno dosáhlo připojení. Pokud tato kontrola selže, je třeba provést druhou kontrolu, aby se zjistilo, zda při připojení došlo k chybě. Pokud ne, a pokus o připojení stále pokračuje, časovač se zvýší a po malém spánku bude připojení znovu vyzvednuto.

Tato metoda těžce využívá zpracování výjimek. Pokud dojde k chybě, bude tato výjimka načtena z SocketThread instance a bude znovu vyvolána. Pokud dojde k vypršení časového limitu sítě, metoda vyvolá a java.io.InterruptedIOException.

Následující fragment kódu ukazuje mechanismus dotazování a kód pro zpracování chyb.

for (;;) {// Zkontrolujte, zda je navázáno spojení if (st.isConnected ()) {// Ano ... přiřadit proměnné sock a vymanit se ze smyčky sock = st.getSocket (); přestávka; } else {// Zkontrolujte, zda nedošlo k chybě if (st.isError ()) {// Nelze navázat spojení throw (st.getException ()); } zkuste {// Spánek na krátkou dobu Thread.sleep (POLL_DELAY); } catch (InterruptedException ie) {} // Časovač přírůstkového časovače + = POLL_DELAY; // Zkontrolujte, zda nebyl překročen časový limit if (timer> delay) {// Nelze se připojit k serveru vyvolá novou InterruptedIOException ("Could not connect for" + delay + "milliseconds"); }}} 

Uvnitř zablokovaného vlákna

Zatímco připojení je pravidelně dotazováno, druhé vlákno se pokusí vytvořit novou instanci java.net.Socket. K dispozici jsou metody přístupového modulu k určení stavu připojení a také k získání konečného připojení soketu. The SocketThread.isConnected () metoda vrací booleovskou hodnotu, která označuje, zda bylo navázáno připojení, a SocketThread.getSocket () metoda vrací a Zásuvka. Podobné metody jsou k dispozici k určení, zda došlo k chybě, a k přístupu k zachycené výjimce.

Všechny tyto metody poskytují kontrolované rozhraní pro SocketThread instance, aniž by bylo možné externě upravovat soukromé členské proměnné. Následující příklad kódu ukazuje vlákna běh() metoda. Kdy a jestli vrací konstruktor soketu a Zásuvka, bude přiřazen k soukromé členské proměnné, ke které přístupové metody poskytují přístup. Při příštím dotazu na stav připojení pomocí SocketThread.isConnected () metodou bude zásuvka k dispozici pro použití. Stejná technika se používá k detekci chyb; Pokud java.io.IOException je chycen, bude uložen v soukromém členovi, ke kterému lze přistupovat přes isError () a getException () přístupové metody.

$config[zx-auto] not found$config[zx-overlay] not found