Ohjelmointi

Verkon aikakatkaisujen helppo hallinta

Monet ohjelmoijat pelkäävät ajatusta verkon aikakatkaisujen käsittelemisestä. Yleinen pelko on, että yksinkertainen, yksisäikeinen verkkoasiakas ilman aikakatkaisutukea siirtyy monimutkaiseksi monisäikeiseksi painajaiseksi, jossa on erilliset säikeet verkon aikakatkaisujen havaitsemiseksi, ja jonkinlainen ilmoitusprosessi työssä estetyn säikeen ja pääsovelluksen välillä. Vaikka tämä on yksi vaihtoehto kehittäjille, se ei ole ainoa vaihtoehto. Verkon aikakatkaisujen käsittelyn ei tarvitse olla vaikea tehtävä, ja monissa tapauksissa voit välttää koodin kirjoittamisen täydellisiin säikeisiin.

Kun työskentelet verkkoyhteyksien tai minkä tahansa tyyppisten I / O-laitteiden kanssa, toimintoja on kaksi luokitusta:

  • Estotoiminnot: Lue tai kirjoita pysähdyksiä, toiminta odottaa, kunnes I / O-laite on valmis
  • Estämättömät toiminnot: Luku- tai kirjoitusyritys suoritetaan, toiminta keskeytyy, jos I / O-laite ei ole valmis

Java-verkko on oletusarvoisesti I / O-eston muoto. Siten, kun Java-verkkosovellus lukee pistorasiayhteydestä, se yleensä odottaa loputtomasti, ellei ole välitöntä vastausta. Jos tietoja ei ole käytettävissä, ohjelma jatkaa odottamista eikä mitään lisätoimia voida tehdä. Yksi ratkaisu, joka ratkaisee ongelman, mutta tuo hieman ylimääräistä monimutkaisuutta, on saada toinen lanka suorittamaan operaatio; tällä tavalla, jos toinen säike tukkeutuu, sovellus voi silti vastata käyttäjän komentoihin tai jopa lopettaa pysähtyneen langan tarvittaessa.

Tätä ratkaisua käytetään usein, mutta on olemassa paljon yksinkertaisempi vaihtoehto. Java tukee myös estämättömän verkon I / O: ta, joka voidaan aktivoida missä tahansa Pistoke, ServerSockettai DatagramSocket. On mahdollista määrittää enimmäisaika, jonka luku- tai kirjoitusoperaatio pysähtyy, ennen kuin ohjaus palautetaan takaisin sovellukseen. Verkkoasiakkaille tämä on helpoin ratkaisu ja tarjoaa yksinkertaisemman, hallittavamman koodin.

Ainoa haitta estämättömälle I / O-verkon I / O: lle on, että se vaatii olemassa olevan pistorasian. Siten, vaikka tämä menetelmä on täydellinen normaaleille luku- tai kirjoitusoperaatioille, yhdistämistoiminto voi pysähtyä paljon pidempään, koska ei ole olemassa menetelmää aikakatkaisuajan määrittämiseksi yhdistämistoiminnoille. Monet sovellukset vaativat tätä kykyä; voit kuitenkin välttää helposti ylimääräisen koodin kirjoittamisen. Olen kirjoittanut pienen luokan, jonka avulla voit määrittää yhteyden aikakatkaisuarvon. Siinä käytetään toista säiettä, mutta sisäiset yksityiskohdat erotetaan. Tämä lähestymistapa toimii hyvin, koska se tarjoaa estämättömän I / O-käyttöliittymän ja toisen säikeen yksityiskohdat on piilotettu näkyviltä.

Estämätön verkon I / O

Yksinkertaisin tapa tehdä jotain osoittautuu usein parhaaksi. Vaikka on joskus tarpeen käyttää säikeitä ja estää I / O, useimmissa tapauksissa estämättömät I / O: t sopivat paljon selkeämpään ja tyylikkäämpään ratkaisuun. Vain muutamalla rivillä koodia voit sisällyttää aikakatkaisutuen mihin tahansa pistorasiasovellukseen. Etkö usko minua? Jatka lukemista.

Kun Java 1.1 julkaistiin, se sisälsi API-muutoksia java.net paketti, joka antoi ohjelmoijille mahdollisuuden määrittää pistorasiat. Nämä vaihtoehdot antavat ohjelmoijille paremman hallinnan pistorasiatiedonsiirrosta. Yksi vaihtoehto erityisesti, SO_TIMEOUT, on erittäin hyödyllinen, koska sen avulla ohjelmoijat voivat määrittää ajan, jonka lukutoiminto estää. Voimme määrittää lyhyen viiveen tai ei lainkaan, ja tehdä verkkokoodistamme estoton.

Katsotaanpa, miten tämä toimii. Uusi menetelmä, setSoTimeout (int) on lisätty seuraaviin pistorasialuokkiin:

  • java.net. pistorasia
  • java.net.DatagramSocket
  • java.net.ServerSocket

Tämän menetelmän avulla voimme määrittää aikakatkaisun enimmäispituuden millisekunteina, jonka seuraavat verkkotoiminnot estävät:

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

Aina kun jotain näistä menetelmistä kutsutaan, kello alkaa tikata. Jos toimintoa ei ole estetty, se nollautuu ja käynnistyy uudelleen vasta, kun jompikumpi näistä menetelmistä on kutsuttu uudelleen; seurauksena ei voi olla aikakatkaisu, ellet tee verkon I / O-toimintoa. Seuraava esimerkki osoittaa, kuinka helppoa aikakatkaisujen käsitteleminen voi olla ilman useita suorituslankoja:

// Luo porttiin 2000 datagrammiliitin saapuvien UDP-pakettien kuuntelemiseksi DatagramSocket dgramSocket = new DatagramSocket (2000); // Poista I / O-toimintojen esto käytöstä määrittämällä viiden sekunnin aikakatkaisu dgramSocket.setSoTimeout (5000); 

Aikakatkaisuarvon määrittäminen estää verkkotoimintojamme estymästä loputtomiin. Tässä vaiheessa mietit todennäköisesti mitä tapahtuu, kun verkon toiminta aikakatkaistaan. Sen sijaan, että palauttaisit virhekoodin, jota kehittäjät eivät aina tarkista, a java.io.KeskeytynytIOException heitetään. Poikkeusten käsittely on erinomainen tapa käsitellä virhetilanteita, ja sen avulla voimme erottaa normaalin koodin virheiden käsittelykoodistamme. Sitä paitsi kuka tarkistaa uskonnollisesti jokaisen palautusarvon nollaviitteen suhteen? Heittämällä poikkeuksen kehittäjät joutuvat tarjoamaan saaliiden käsittelijän aikakatkaisuille.

Seuraava koodinpätkä näyttää, kuinka aikakatkaisu käsitellään luettaessa TCP-liitännästä:

// Aseta pistorasian aikakatkaisu kymmenelle sekunnille connection.setSoTimeout (10000); kokeile {// Luo DataInputStream lukemista kannasta DataInputStream din = new DataInputStream (connection.getInputStream ()); // Lue tiedot (;;;) {String line = din.readLine () -tietojen loppuun saakka; if (rivi! = null) System.out.println (rivi); muuten rikkoa; }} // Poikkeus heitetään verkon aikakatkaisun tapahtuessa (InterruptedIOException iioe) {System.err.println ("Etäkone aikakatkaistiin lukutoiminnon aikana"); } // Poikkeus heitetään, kun yleinen verkon I / O-virhe ilmenee. (IOException ioe) {System.err.println ("Verkon I / O-virhe -" + ioe); } 

Vain muutama ylimääräinen koodirivi a: lle yrittää {} catch block, verkon aikakatkaisut on erittäin helppo saada kiinni. Sitten sovellus voi vastata tilanteeseen pysähtymättä. Se voi alkaa esimerkiksi ilmoittamalla käyttäjälle tai yrittämällä luoda uuden yhteyden. Kun käytetään datagrammiliittimiä, jotka lähettävät tietopaketteja ilman, että ne takaavat toimituksen, sovellus voisi vastata verkon aikakatkaisuun lähettämällä uudelleen paketin, joka oli kadonnut kuljetuksen aikana. Tämän aikakatkaisun toteuttaminen vie hyvin vähän aikaa ja johtaa erittäin puhtaaseen ratkaisuun. Ainoa kerta, kun estämätön I / O ei ole optimaalinen ratkaisu, on, kun joudut myös havaitsemaan aikakatkaisut yhdistämistoiminnoissa tai kun kohdeympäristösi ei tue Java 1.1: tä.

Aikakatkaisun käsittely yhdistämistoiminnoissa

Jos tavoitteesi on saavuttaa täydellinen aikakatkaisun havaitseminen ja käsittely, sinun on harkittava yhdistämistoimintoja. Kun luot instanssia java.net. pistorasia, yritetään muodostaa yhteys. Jos isäntäkone on aktiivinen, mutta palvelussa ei ole käynnissä java.net. pistorasia rakentaja, a ConnectionException heitetään ja hallinta palaa sovellukseen. Kuitenkin, jos kone on alhaalla tai jos tälle isännälle ei ole reittiä, pistorasiayhteys aikakatkaistaan ​​itsestään paljon myöhemmin. Sillä välin sovelluksesi pysyy jäädytettynä, eikä aikakatkaisuarvoa voida muuttaa.

Vaikka pistorasian rakentajan kutsu lopulta palaa, se aiheuttaa merkittävän viiveen. Yksi tapa käsitellä tätä ongelmaa on käyttää toista säiettä, joka suorittaa mahdollisesti estävän yhteyden, ja kysyä jatkuvasti tätä säiettä nähdäksesi, onko yhteys muodostettu.

Tämä ei kuitenkaan aina johda tyylikkääseen ratkaisuun. Kyllä, voit muuntaa verkkoasiakkaat monisäikeisiksi sovelluksiksi, mutta usein tähän tarvitaan paljon ylimääräistä työtä. Se tekee koodista monimutkaisemman, ja kun kirjoitetaan vain yksinkertainen verkkosovellus, vaadittavan työn määrää on vaikea perustella. Jos kirjoitat paljon verkkosovelluksia, saatat löytää itsesi keksimään pyörää usein. On kuitenkin olemassa yksinkertaisempi ratkaisu.

Olen kirjoittanut yksinkertaisen, uudelleenkäytettävän luokan, jota voit käyttää omissa sovelluksissasi. Luokka muodostaa TCP-liitäntäyhteyden pysähtymättä pitkiä aikoja. Soitat yksinkertaisesti a getSocket menetelmä, määritetään isäntänimi, portti ja aikakatkaisuviive ja vastaanotetaan liitäntä. Seuraava esimerkki näyttää yhteyspyynnön:

// Yhdistä etäpalvelimeen isäntänimellä neljän sekunnin aikakatkaisun kautta Socket-yhteys = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Jos kaikki menee hyvin, pistoke palautetaan, kuten tavallinen java.net. pistorasia rakentajat. Jos yhteyttä ei voida muodostaa ennen määritettyä aikakatkaisua, menetelmä lopetetaan ja heittää java.io.KeskeytynytIOException, aivan kuten muutkin pistorasianlukutoiminnot tekisivät, kun aikakatkaisu on määritetty käyttämällä a setSoTimeout menetelmä. Melko helppoa, vai mitä?

Kapseloidaan monisäikeinen verkkokoodi yhteen luokkaan

Samalla kun TimedSocket luokka on itsessään hyödyllinen komponentti, se on myös erittäin hyvä oppimisväline ymmärtääkseen, miten I / O: n estämistä käsitellään. Kun estotoiminto suoritetaan, yksisäikeinen sovellus lukittuu toistaiseksi. Jos käytetään useita suorituslankoja, vain yksi säie tarvitsee kuitenkin jumissa. toinen säie voi jatkaa suorittamista. Katsotaanpa miten TimedSocket luokan töitä.

Kun sovelluksen on muodostettava yhteys etäpalvelimeen, se käynnistää TimedSocket.getSocket () menetelmä ja välittää etäisännän ja -portin tiedot. getSocket () menetelmä on ylikuormitettu, jolloin molemmat a Merkkijono isäntänimi ja InetAddress täsmennettävä. Tämän parametrialueen tulisi olla riittävä suurimmalle osalle pistorasiatoimintoja, vaikka erityisiä toteutuksia varten voitaisiin lisätä mukautettua ylikuormitusta. Sisällä getSocket () -menetelmällä luodaan toinen ketju.

Mielikuvituksellisesti nimetty SocketThread luo instanssin java.net. pistorasia, joka voi mahdollisesti estää huomattavan pitkän ajan. Se tarjoaa käyttöoikeusmenetelmiä sen määrittämiseksi, onko yhteys muodostettu vai onko tapahtunut virhe (esimerkiksi jos java.net.SocketException heitettiin yhteyden aikana).

Yhteyden muodostamisen aikana ensisijainen säie odottaa yhteyden muodostumista, virheen esiintymistä tai verkon aikakatkaisua. Sadan millisekunnin välein tarkistetaan, onko toinen säie saavuttanut yhteyden. Jos tämä tarkistus epäonnistuu, on tehtävä toinen tarkistus sen selvittämiseksi, tapahtuiko yhteydessä virhe. Jos ei, ja yhteysyritys jatkuu edelleen, ajastinta lisätään ja pienen unen jälkeen yhteys kysytään uudelleen.

Tässä menetelmässä käytetään paljon poikkeusten käsittelyä. Jos tapahtuu virhe, tämä poikkeus luetaan SocketThread esimerkiksi, ja se heitetään uudelleen. Jos tapahtuu verkon aikakatkaisu, menetelmä heittää a java.io.KeskeytynytIOException.

Seuraava koodinpätkä näyttää kyselymekanismin ja virhekäsittelykoodin.

for (;;) {// Tarkista, onko yhteys muodostettu, jos (st.isConnected ()) {// Kyllä ... määritetään sukkamuuttujalle ja irrotetaan silmukan sukasta = st.getSocket (); tauko; } else {// Tarkista, tapahtuiko virhe, jos (st.isError ()) {// Yhteyttä ei voitu luoda heittää (st.getException ()); } kokeile {// nukkua lyhyen aikaa Thread.sleep (POLL_DELAY); } catch (InterruptedException ie) {} // Kasvuajastimen ajastin + = POLL_DELAY; // Tarkista, onko aikaraja ylitetty, jos (ajastin> viive) {// Ei voida muodostaa yhteyttä palvelimeen, heittää uusi InterruptedIOException ("Ei voitu muodostaa yhteyttä" + viive + "millisekuntia"); }}} 

Tukkeutuneen langan sisällä

Vaikka yhteys kysytään säännöllisesti, toinen säike yrittää luoda uuden java.net. pistorasia. Lisätoimintamenetelmät ovat käytettävissä yhteyden tilan määrittämiseksi sekä lopullisen pistorasiakytkennän saamiseksi. SocketThread.isConnected () method palauttaa loogisen arvon osoittamaan, onko yhteys muodostettu, ja SocketThread.getSocket () method palauttaa a Pistoke. Samanlaisia ​​menetelmiä tarjotaan virheen selvittämiseksi ja kiinni jääneen poikkeuksen käyttämiseksi.

Kaikki nämä menetelmät tarjoavat hallitun käyttöliittymän SocketThread esimerkiksi sallimatta yksityisten jäsenten muuttujien ulkoista muokkaamista. Seuraava koodiesimerkki näyttää ketjun juosta() menetelmä. Milloin ja jos, pistorasian rakentaja palauttaa a Pistoke, se osoitetaan yksityiselle jäsenmuuttujalle, johon accessor-menetelmät tarjoavat pääsyn. Seuraavan kerran, kun yhteyden tilaa kysytään, käyttämällä SocketThread.isConnected () menetelmällä, pistoke on käytettävissä. Samaa tekniikkaa käytetään virheiden havaitsemiseen; jos java.io.IOException on kiinni, se tallennetaan yksityiseen jäseneen, johon pääsee isError () ja getException () lisävarustemenetelmät.