Programovanie

Jednoduché spracovanie časových limitov siete

Mnoho programátorov sa bojí myšlienky na riešenie časového limitu siete. Všeobecne sa obávame, že jednoduchý sieťový klient s jedným vláknom bez podpory vypršania časového limitu sa nafúkne do zložitej nočnej mory s viacerými vláknami so samostatnými vláknami potrebnými na detekciu časových limitov siete a nejaká forma procesu oznámenia medzi zablokovaným vláknom a hlavnou aplikáciou. Aj keď je to jedna možnosť pre vývojárov, nie je to jediná. Riešenie časových limitov siete nemusí byť náročná úloha a v mnohých prípadoch sa môžete úplne vyhnúť písaniu kódu pre ďalšie vlákna.

Pri práci so sieťovými pripojeniami alebo s akýmkoľvek typom I / O zariadenia existujú dve klasifikácie operácií:

  • Blokovacie operácie: Stánky na čítanie alebo zápis, prevádzka čaká, kým nebude pripravené I / O zariadenie
  • Neblokujúce operácie: Uskutočnil sa pokus o čítanie alebo zápis, prevádzka sa preruší, ak I / O zariadenie nie je pripravené

Sieť Java je štandardne formou blokovania I / O. Keď teda sieťová aplikácia Java číta zo soketového pripojenia, bude spravidla čakať neurčito, ak nebude mať okamžitú odpoveď. Ak nie sú k dispozícii žiadne údaje, program bude čakať a nebude možné ďalej pracovať. Jedným z riešení, ktoré problém vyrieši, ale prinesie trochu viac zložitosti, je nechať operáciu vykonať druhým vláknom; týmto spôsobom, ak dôjde k zablokovaniu druhého vlákna, môže aplikácia stále odpovedať na príkazy používateľov alebo v prípade potreby dokonca ukončiť zablokované vlákno.

Toto riešenie sa často používa, ale existuje oveľa jednoduchšia alternatíva. Java podporuje aj neblokujúce sieťové I / O, ktoré je možné aktivovať na ľubovoľnom Zásuvka, ServerSocketalebo DatagramSocket. Je možné určiť maximálny čas, po ktorý sa zastaví operácia čítania alebo zápisu pred vrátením ovládacieho prvku späť do aplikácie. Pre sieťových klientov je to najjednoduchšie riešenie a ponúka jednoduchší a lepšie spravovateľný kód.

Jedinou nevýhodou neblokujúcich sieťových I / O v prostredí Java je to, že vyžaduje existujúci soket. Aj keď je teda táto metóda dokonalá pre bežné operácie čítania alebo zápisu, operácia pripojenia sa môže pozastaviť na oveľa dlhšie obdobie, pretože neexistuje žiadna metóda na určenie časového limitu pre operácie pripojenia. Mnoho aplikácií vyžaduje túto schopnosť; môžete sa však ľahko vyhnúť ďalšej práci s písaním dodatočného kódu. Napísal som malú triedu, ktorá vám umožňuje určiť hodnotu časového limitu pre pripojenie. Používa druhé vlákno, ale interné detaily sú abstrahované. Tento prístup funguje dobre, pretože poskytuje neblokujúce I / O rozhranie a podrobnosti druhého vlákna sú skryté.

Neblokujúce sieťové I / O

Najjednoduchší spôsob, ako niečo robiť, sa často ukáže ako najlepší spôsob. Aj keď je niekedy potrebné použiť vlákna a blokovať I / O, vo väčšine prípadov sa neblokujúce I / O dajú využiť na oveľa jasnejšie a elegantnejšie riešenie. Iba s niekoľkými riadkami kódu môžete začleniť podporu časového limitu pre akúkoľvek aplikáciu soketu. Neverte mi? Pokračuj v čítaní.

Keď bola vydaná Java 1.1, obsahovala zmeny API v java.net balík, ktorý programátorom umožňoval špecifikovať možnosti soketov. Tieto možnosti poskytujú programátorom väčšiu kontrolu nad soketovou komunikáciou. Jedna možnosť, najmä SO_TIMEOUT, je mimoriadne užitočné, pretože umožňuje programátorom určiť čas, ktorý bude operácia čítania blokovať. Môžeme určiť krátke alebo vôbec žiadne oneskorenie a náš sieťový kód zmeniť na neblokujúci.

Poďme sa pozrieť na to, ako to funguje. Nová metóda, setSoTimeout (int) bol pridaný do nasledujúcich tried soketov:

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

Táto metóda nám umožňuje určiť maximálnu dĺžku časového limitu v milisekundách, ktorú budú blokovať nasledujúce sieťové operácie:

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

Kedykoľvek sa volá jedna z týchto metód, hodiny začnú tikať. Ak operácia nie je blokovaná, obnoví sa a reštartuje sa až po opätovnom vyvolaní jednej z týchto metód; vo výsledku tak nikdy nemôže dôjsť k žiadnemu časovému limitu, pokiaľ nevykonáte sieťovú I / O operáciu. Nasledujúci príklad ukazuje, aké ľahké je zvládnuť časové limity bez toho, aby ste sa uchýlili k použitiu viacerých vlákien vykonania:

// Vytvorte zásuvku datagramu na porte 2000, aby ste počúvali prichádzajúce pakety UDP DatagramSocket dgramSocket = nový DatagramSocket (2000); // Zakážte blokovanie I / O operácií zadaním päťsekundového časového limitu dgramSocket.setSoTimeout (5000); 

Priradenie hodnoty časového limitu zabráni blokovaniu našich sieťových operácií na neurčito. V tejto chvíli ste pravdepodobne zvedaví, čo sa stane, keď vyprší čas prevádzky siete. Namiesto vrátenia chybového kódu, ktorý vývojári nemusia vždy skontrolovať, a java.io.InterruptedIOException je hodená. Spracovanie výnimiek je vynikajúci spôsob riešenia chybových stavov a umožňuje nám oddeliť náš normálny kód od nášho kódu na spracovanie chýb. Okrem toho, kto nábožensky kontroluje každú návratovú hodnotu, či neobsahuje nulovú hodnotu? Odvolaním výnimky sú vývojári nútení poskytnúť obslužnú rutinu úlovkov pre časové limity.

Nasledujúci úryvok kódu ukazuje, ako zvládnuť operáciu časového limitu pri čítaní zo soketu TCP:

// Nastavte časový limit zásuvky na desať sekúnd connection.setSoTimeout (10 000); vyskúšajte {// Vytvorte DataInputStream na čítanie zo soketu DataInputStream din = nový DataInputStream (connection.getInputStream ()); // Čítať údaje až do konca údajov pre (;;) {String line = din.readLine (); if (riadok! = null) System.out.println (riadok); else break; }} // Výnimka vyvolaná, keď dôjde k timeoutu siete catch (InterruptedIOException iioe) {System.err.println ("Časový limit vzdialeného hostiteľa vypršal počas operácie čítania"); } // Výnimka vyvolaná, keď dôjde k všeobecnej chybe I / O siete chybou (IOException ioe) {System.err.println ("Chyba I / O siete -" + ioe); } 

Iba s niekoľkými riadkami kódu navyše pre a skúsiť {} catch block, je mimoriadne ľahké zachytiť timeouty siete. Aplikácia potom môže reagovať na situáciu bez toho, aby sa zastavila. Mohlo by sa to napríklad začať oznámením používateľovi alebo pokusom o nadviazanie nového spojenia. Pri použití zásuviek datagramu, ktoré odosielajú pakety informácií bez zaručenia doručenia, mohla aplikácia reagovať na časový limit siete opätovným odoslaním paketu, ktorý sa stratil pri prenose. Implementácia tohto časového limitu trvá veľmi málo času a vedie k veľmi čistému riešeniu. Jediným časom, kedy neblokujúce I / O nie je optimálnym riešením, je okamih, keď tiež potrebujete zistiť časové limity pri operáciách pripojenia, alebo keď vaše cieľové prostredie nepodporuje Java 1.1.

Spracovanie časového limitu pri operáciách pripojenia

Ak je vaším cieľom dosiahnutie úplnej detekcie a spracovania časového limitu, budete musieť zvážiť operácie pripojenia. Pri vytváraní inštancie java.net.Socket, dôjde k pokusu o nadviazanie spojenia. Ak je hostiteľský počítač aktívny, ale na porte uvedenom v serveri nie je spustená žiadna služba java.net.Socket konštruktér, a ConnectionException bude vyhodené a kontrola sa vráti do aplikácie. Ak je však počítač vypnutý alebo ak k tomuto hostiteľovi neexistuje žiadna cesta, pripojenie soketu nakoniec vyprší samo o sebe oveľa neskôr. Medzitým zostane vaša aplikácia zmrazená a nie je možné nijako zmeniť hodnotu časového limitu.

Aj keď sa volanie konštruktora soketu nakoniec vráti, prináša to značné oneskorenie. Jedným zo spôsobov riešenia tohto problému je použitie druhého vlákna, ktoré vykoná potenciálne blokujúce pripojenie, a neustále dotazovanie tohto vlákna, aby sa zistilo, či bolo pripojenie nadviazané.

To však nemusí vždy viesť k elegantnému riešeniu. Áno, svojich sieťových klientov môžete previesť na viacvláknové aplikácie, ale množstvo potrebných ďalších prác je často neúnosné. Robí to kód zložitejším, a keď píšete iba jednoduchú sieťovú aplikáciu, je potrebné vynaložiť značné úsilie. Ak píšete veľa sieťových aplikácií, mohli by ste často objavovať koleso znova. Existuje však jednoduchšie riešenie.

Napísal som jednoduchú, opakovane použiteľnú triedu, ktorú môžete použiť vo svojich vlastných aplikáciách. Trieda generuje pripojenie TCP soketu bez dlhodobého zastavenia. Jednoducho zavoláte a getSocket metóda, špecifikujúca názov hostiteľa, port a oneskorenie časového limitu, a prijať soket. Nasledujúci príklad zobrazuje žiadosť o pripojenie:

// Pripojte sa k vzdialenému serveru podľa názvu hostiteľa s časovým limitom štyroch sekúnd Socket connection = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Ak všetko pôjde dobre, zásuvka sa vráti, rovnako ako štandard java.net.Socket konštruktéri. Ak sa spojenie nepodarí nadviazať skôr, ako nastane zadaný časový limit, metóda sa zastaví a hodí znak java.io.InterruptedIOException, rovnako ako iné operácie čítania soketu, keď by bol zadaný časový limit pomocou a setSoTimeout metóda. Celkom ľahké, hm?

Zapuzdrenie viacvláknového sieťového kódu do jednej triedy

Kým TimedSocket trieda je sama o sebe užitočnou súčasťou, je to tiež veľmi dobrá učebná pomôcka na pochopenie toho, ako sa vysporiadať s blokovaním I / O. Keď sa vykoná operácia blokovania, aplikácia s jedným vláknom sa zablokuje na neurčito. Ak sa používa viac vykonávacích vlákien, je potrebné pozastaviť iba jedno vlákno; druhé vlákno môže pokračovať vo vykonávaní. Poďme sa pozrieť na to, ako TimedSocket triedne diela.

Ak sa aplikácia potrebuje pripojiť k vzdialenému serveru, vyvolá TimedSocket.getSocket () metóda a odovzdá podrobnosti o vzdialenom hostiteľovi a porte. The getSocket () metóda je preťažená, čo umožňuje a String meno hostiteľa a InetAddress upresniť. Tento rozsah parametrov by mal byť dostatočný pre väčšinu operácií soketu, aj keď pre špeciálne implementácie je možné pridať vlastné preťaženie. Vnútri getSocket () metódou sa vytvorí druhé vlákno.

Nápadito pomenované SocketThread vytvorí inštanciu java.net.Socket, ktoré môžu potenciálne blokovať na značnú dobu. Poskytuje prístupové metódy na určenie, či bolo nadviazané pripojenie alebo či došlo k chybe (napríklad ak java.net.SocketException bol vyhodený počas pripojenia).

Počas vytvárania pripojenia primárne vlákno počká, kým sa nadviaže pripojenie, či sa vyskytne chyba alebo vyprší časový limit siete. Každých sto milisekúnd sa kontroluje, či druhé vlákno dosiahlo spojenie. Ak táto kontrola zlyhá, je potrebné vykonať druhú kontrolu, aby sa zistilo, či došlo k chybe v pripojení. Ak nie, a pokus o pripojenie stále pokračuje, časovač sa zvýši a po malom spánku sa spojenie znova vyzve.

Táto metóda veľmi využíva spracovanie výnimiek. Ak sa vyskytne chyba, táto výnimka sa načíta z adresy SocketThread inštancia, a bude sa vhadzovať znova. Ak dôjde k vypršaniu časového limitu siete, metóda vyvolá a java.io.InterruptedIOException.

Nasledujúci úryvok kódu zobrazuje mechanizmus dopytovania a kód na spracovanie chýb.

for (;;) {// Skontrolujte, či je nadviazané spojenie if (st.isConnected ()) {// Áno ... priradiť k premennej sock, a vymaniť sa zo slučky sock = st.getSocket (); prestávka; } else {// Skontrolujte, či nedošlo k chybe if (st.isError ()) {// Nie je možné nadviazať žiadne spojenie throw (st.getException ()); } vyskúšajte {// Krátkodobý spánok Thread.sleep (POLL_DELAY); } catch (InterruptedException ie) {} // Časovač prírastku časovača + = POLL_DELAY; // Skontrolujte, či nebol prekročený časový limit if (timer> delay) {// Nemôžem sa pripojiť k serveru vyvolať novú InterruptedIOException ("Could not connect for" + delay + "milliseconds"); }}} 

Vo vnútri zablokovanej nite

Aj keď je pripojenie pravidelne dotazované, druhé vlákno sa pokúša vytvoriť novú inštanciu súboru java.net.Socket. Poskytujú sa prístupové metódy na zisťovanie stavu pripojenia a na získanie konečného pripojenia soketu. The SocketThread.isConnected () metóda vracia boolovskú hodnotu, ktorá označuje, či bolo nadviazané pripojenie, a znak SocketThread.getSocket () metóda vracia a Zásuvka. Podobné metódy sú poskytované na určenie, či došlo k chybe, a na prístup k zachytenej výnimke.

Všetky tieto metódy poskytujú kontrolované rozhranie s internetom SocketThread inštancie bez umožnenia externej úpravy súkromných členských premenných. Nasledujúci príklad kódu ukazuje vlákna run () metóda. Kedy a ak vráti sokový konštruktor a Zásuvka, bude priradený k súkromnej členskej premennej, ku ktorej prístupové metódy poskytujú prístup. Pri ďalšej otázke na stav pripojenia pomocou SocketThread.isConnected () spôsobom bude zásuvka k dispozícii na použitie. Rovnaká technika sa používa na zisťovanie chýb; Ak java.io.IOException je chytený, bude uložený v súkromnom členovi, ku ktorému je prístup cez isError () a getException () prístupové metódy.

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