Programavimas

Paprastas tinklo skirtasis laikas

Daugelis programuotojų bijo minčių, kaip tvarkyti tinklo skirtąjį laiką. Dažniausiai baiminamasi, kad paprastas, vienos gijos tinklo klientas be skirtojo laiko palaikymo pateks į sudėtingą daugiasluoksnį košmarą, turintį atskiras gijas, reikalingas tinklo skirtojo laiko aptikimui, ir tam tikrą pranešimo procesą tarp užblokuoto gijos ir pagrindinės programos. Nors tai yra viena galimybė kūrėjams, ji nėra vienintelė. Tinklo skirtojo laiko nustatymas neturi būti sunki užduotis, ir daugeliu atvejų galite visiškai išvengti papildomų gijų kodo rašymo.

Dirbant su tinklo jungtimis ar bet kokio tipo įvesties / išvesties įrenginiais, yra dvi operacijų klasifikacijos:

  • Blokavimo operacijos: Skaitykite arba rašykite gardus, operacija laukia, kol įvesties / išvesties įrenginys bus paruoštas
  • Neblokavimo operacijos: Bandoma skaityti ar rašyti, operacija nutraukiama, jei įvesties / išvesties įrenginys nėra paruoštas

„Java“ tinklas yra pagal nutylėjimą įvesties / išvesties blokavimo forma. Taigi, kai „Java“ tinklo programa nuskaito iš lizdo jungties, ji paprastai laukia neribotą laiką, jei nebus nedelsiant atsakyta. Jei duomenų nėra, programa toliau lauks ir daugiau dirbti nebus galima. Vienas iš sprendimų, kuris išsprendžia problemą, tačiau įneša šiek tiek papildomo sudėtingumo, yra tai, kad operaciją atliktų antroji gija; Tokiu būdu, jei antroji gija bus užblokuota, programa vis tiek gali atsakyti į vartotojo komandas arba net nutraukti užstrigusį giją, jei reikia.

Šis sprendimas dažnai naudojamas, tačiau yra daug paprastesnė alternatyva. „Java“ taip pat palaiko neužblokuotą tinklo įvestį / išvestį, kurią galima įjungti bet kuriame Lizdas, „ServerSocket“arba „DatagramSocket“. Galima nurodyti, kiek laiko užtruks skaitymo ar rašymo operacija, prieš grąžinant valdymą atgal į programą. Tinklo klientams tai yra paprasčiausias sprendimas ir siūlo paprastesnį, lengviau valdomą kodą.

Vienintelis neblokuojamo tinklo įvesties / išvesties trūkumas naudojant „Java“ yra tas, kad tam reikalingas esamas lizdas. Taigi, nors šis metodas puikiai tinka įprastoms skaitymo ar rašymo operacijoms, prisijungimo operacija gali užstrigti daug ilgiau, nes nėra metodo, kaip nurodyti jungties operacijų skirtąjį laiką. Daugelis programų reikalauja šio gebėjimo; tačiau jūs galite lengvai išvengti papildomo darbo rašydami papildomą kodą. Parašiau mažą klasę, leidžiančią nurodyti ryšio skirtojo laiko vertę. Tam naudojama antroji gija, tačiau vidinės detalės yra nutrauktos. Šis metodas veikia gerai, nes suteikia neužblokuojamą įvesties / išvesties sąsają, o antrojo gijos detalės yra paslėptos.

Nblokuojamas tinklo įvestis / išvestis

Paprasčiausias būdas ką nors padaryti dažnai pasirodo pats geriausias būdas. Nors kartais reikia naudoti gijas ir blokuoti įvestį / išvestį, daugeliu atvejų neblokavimas įvesties / išvesties yra kur kas aiškesnis ir elegantiškesnis sprendimas. Turėdami tik kelias kodo eilutes, galite įtraukti skirtojo laiko palaikymą bet kuriai „Socket“ programai. Netiki manimi? Skaityk.

Kai buvo išleista „Java 1.1“, į ją buvo įtraukti API pakeitimai java.net paketas, leidęs programuotojams nurodyti lizdo parinktis. Šios parinktys leidžia programuotojams geriau kontroliuoti lizdo ryšį. Vienas variantas, SO_TIMEOUT, yra nepaprastai naudinga, nes leidžia programuotojams nurodyti laiką, kurį užblokuos skaitymo operacija. Mes galime nurodyti trumpą delsą arba visai jos nedaryti, o mūsų tinklo kodas nebus blokuojamas.

Pažvelkime, kaip tai veikia. Naujas metodas, setSoTimeout (int) buvo pridėtas prie šių lizdų klasių:

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

Šis metodas leidžia mums nurodyti maksimalų skirtojo laiko trukmę milisekundėmis, kurią blokuos šios tinklo operacijos:

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

Kai tik vadinamas vienas iš šių metodų, laikrodis pradeda tikti. Jei operacija nebus užblokuota, ji bus atstatyta ir paleista iš naujo tik tada, kai vėl bus iškviestas vienas iš šių metodų; todėl jokie skirtasis laikas negali atsirasti, nebent atliksite tinklo įvesties / išvesties operaciją. Šis pavyzdys parodo, kaip paprasta gali būti skirtasis laikas, nenaudojant kelių vykdymo gijų:

// Sukurkite 2000 m. Prievado datagramų lizdą, kad galėtumėte klausytis gaunamų UDP paketų. DatagramSocket dgramSocket = new DatagramSocket (2000); // Išjungti I / O operacijų blokavimą nurodant penkių sekundžių skirtąjį laiką dgramSocket.setSoTimeout (5000); 

Skiriant skirtojo laiko vertę, mūsų tinklo operacijos neleidžia blokuoti neribotą laiką. Šiuo metu jūs tikriausiai domitės, kas nutiks, kai pasibaigs tinklo operacija. Užuot grąžinęs klaidos kodą, kurį kūrėjai ne visada gali patikrinti, a java.io.PertrauktaIOException yra išmestas. Išimčių tvarkymas yra puikus būdas spręsti klaidų sąlygas ir leidžia mums atskirti įprastą kodą nuo klaidų tvarkymo kodo. Be to, kas religiškai tikrina kiekvieną grąžinimo vertę, ar nėra jokios nuorodos? Išmetę išimtį, kūrėjai yra priversti pateikti pagavimo tvarkyklę skirtam laikui.

Šis kodo fragmentas parodo, kaip tvarkyti skirtojo laiko operaciją skaitant iš TCP lizdo:

// Nustatykite dešimties sekundžių jungties skirtąjį laiką.setSoTimeout (10000); pabandykite {// Sukurti DataInputStream skaitymui iš lizdo DataInputStream din = new DataInputStream (connection.getInputStream ()); // Skaityti duomenis iki (; ;;) {String line = din.readLine () duomenų pabaigos; if (eilutė! = null) System.out.println (eilutė); dar lūžti; }} // Išimtis, metama, kai atsiranda tinklo skirtasis laikas (InterruptedIOException iioe) {System.err.println ("Nuotolinio kompiuterio laikas baigėsi skaitymo operacijos metu"); } // Išimtis, kai įvyksta bendrosios tinklo įvesties / išvesties klaida (IOException ioe) {System.err.println ("tinklo įvesties / išvesties klaida -" + ioe); } 

Turint tik keletą papildomų kodo eilučių a bandyti {} sugavimo bloką, nepaprastai lengva sugauti tinklo skirtąjį laiką. Tuomet paraiška gali reaguoti į situaciją neužstrigdama. Pavyzdžiui, tai gali prasidėti pranešant vartotojui arba bandant užmegzti naują ryšį. Naudodama datagramų lizdus, ​​kurie siunčia informacijos paketus negarantuodami pristatymo, programa galėtų atsakyti į tinklo skirtąjį laiką išsiųsdama paketą, kuris buvo pamestas perduodant. Šios pertraukos palaikymo įgyvendinimas užima labai mažai laiko ir leidžia rasti labai švarų sprendimą. Iš tiesų, vienintelis laikas, kai blokavimas įvesties / išvesties nėra optimalus sprendimas, yra tada, kai taip pat reikia aptikti prisijungimo operacijų skirtąjį laiką arba kai jūsų tikslinė aplinka nepalaiko „Java 1.1“.

Skirtojo prisijungimo operacijų skirtasis laikas

Jei jūsų tikslas yra nustatyti visišką skirtojo laiko nustatymą ir tvarkymą, turėsite apsvarstyti prisijungimo operacijas. Kuriant java.net.Socket, bandoma užmegzti ryšį. Jei pagrindinė mašina yra aktyvi, bet joje nurodytame prievade neveikia jokia paslauga java.net.Socket konstruktorius, a „ConnectionException“ bus išmestas ir kontrolė grįš į programą. Tačiau, jei mašina neveikia arba jei nėra kelio į tą pagrindinį kompiuterį, lizdo ryšys galų gale pasibaigs daug vėliau. Tuo tarpu jūsų programa lieka užšaldyta ir jokiu būdu negalima pakeisti skirtojo laiko vertės.

Nors lizdo konstruktoriaus skambutis galų gale grįš, tai sukelia didelį vėlavimą. Vienas iš būdų spręsti šią problemą yra panaudoti antrą giją, kuri atliks potencialiai blokuojantį ryšį, ir nuolat apklausti tą giją, kad būtų nustatyta, ar ryšys užmegztas.

Tačiau tai ne visada lemia elegantišką sprendimą. Taip, jūs galite konvertuoti savo tinklo klientus į daugiasluoksnes programas, tačiau dažnai tam reikalingas papildomas darbas yra pernelyg didelis. Tai daro kodą sudėtingesnį, o kai rašoma tik paprasta tinklo programa, sunku pagrįsti reikalingų pastangų kiekį. Jei rašote daug tinklo programų, pastebėtumėte, kad dažnai išradinėjate ratą. Tačiau yra paprastesnis sprendimas.

Parašiau paprastą, daugkartinio naudojimo klasę, kurią galite naudoti savo programose. Klasė sukuria TCP lizdo jungtį be strigimo ilgą laiką. Paprasčiausiai paskambinsite a „getSocket“ metodas, nurodant pagrindinio kompiuterio pavadinimą, prievadą ir skirtąjį laiką ir gaunant lizdą. Šiame pavyzdyje rodoma ryšio užklausa:

// Prisijunkite prie nuotolinio serverio pagal pagrindinio kompiuterio pavadinimą, naudodamiesi keturių sekundžių skirtojo laiko „Socket“ ryšiu = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Jei viskas gerai, lizdas bus grąžintas, kaip ir standartinis java.net.Socket konstruktoriai. Jei nepavyksta užmegzti ryšio prieš nurodytą skirtąjį laiką, metodas bus sustabdytas ir užmes java.io.PertrauktaIOException, kaip ir kitos „socket-read“ operacijos, kai skirtasis laikas nurodomas naudojant a „setSoTimeout“ metodas. Gana lengva, ar ne?

Daugelio gijų tinklo kodo sujungimas į vieną klasę

Kol „TimedSocket“ klasė yra savaime naudingas komponentas, ji taip pat yra labai gera mokymosi priemonė, skirta suprasti, kaip elgtis blokuojant I / O. Atlikus blokavimo operaciją, vienos gijos programa bus neribotai blokuojama. Tačiau jei naudojamos kelios vykdymo gijos, reikia užstrigti tik vienai gijai; kita gija gali tęsti vykdymą. Pažvelkime, kaip „TimedSocket“ klasės darbai.

Kai programai reikia prisijungti prie nuotolinio serverio, ji iškviečia „TimedSocket.getSocket“ () metodą ir perduoda išsamią informaciją apie nuotolinį pagrindinį kompiuterį ir prievadą. „getSocket“ () metodas yra perkrautas, leidžiantis tiek a Stygos pagrindinio kompiuterio vardas ir „InetAddress“ reikia patikslinti. Šio parametrų diapazono turėtų pakakti daugumai „Socket“ operacijų, tačiau specialiems diegimams gali būti pridėta pasirinktinė perkrova. Viduje „getSocket“ () metodas, sukuriama antroji gija.

Vaizduojamai pavadintas „SocketThread“ sukurs java.net.Socket, kuris gali užblokuoti ilgą laiką. Jame pateikiami prieigos metodai, skirti nustatyti, ar ryšys užmegztas, ar įvyko klaida (pavyzdžiui, jei java.net.SocketException buvo mesta prisijungimo metu).

Kol ryšys užmezgamas, pagrindinis gija laukia, kol bus užmegztas ryšys, atsiras klaida arba pasibaigs tinklo skirtasis laikas. Kas šimtas milisekundžių tikrinama, ar antrasis siūlas pasiekė ryšį. Jei šis patikrinimas nepavyksta, reikia atlikti antrą patikrinimą, siekiant nustatyti, ar ryšyje įvyko klaida. Jei ne, o bandymas prisijungti vis dar tęsiasi, laikmatis padidinamas, o po nedidelio miego ryšys vėl bus apklausiamas.

Šis metodas labai naudoja išimčių tvarkymą. Jei įvyksta klaida, ši išimtis bus skaitoma iš „SocketThread“ ir vėl bus išmestas. Jei atsiras tinklo skirtasis laikas, metodas bus a java.io.PertrauktaIOException.

Šis kodo fragmentas rodo apklausos mechanizmą ir klaidų tvarkymo kodą.

for (;;) {// Patikrinkite, ar ryšys užmegztas, jei (st.isConnected ()) {// Taip ... priskirti kojinių kintamajam ir išsivaduoti iš kilpos kojinės = st.getSocket (); pertrauka; } else {// Patikrinkite, ar neįvyko klaida, jei (st.isError ()) {// Nepavyko užmegzti ryšio metimas (st.getException ()); } pabandykite {// Miegoti trumpą laiką Thread.sleep (POLL_DELAY); } catch (InterruptedException ie) {} // Prieaugio laikmačio laikmatis + = POLL_DELAY; // Patikrinkite, ar neviršytas laiko limitas, jei (laikmatis> delsa) {// Nepavyksta prisijungti prie serverio, kad būtų sukurta nauja „InterruptedIOException“ („Nepavyko prisijungti už + vėlavimas +„ milisekundės “); }}} 

Užblokuoto sriegio viduje

Nors ryšys reguliariai apklausiamas, antroji gija bando sukurti naują java.net.Socket. Pateikti prieigos metodai, skirti nustatyti ryšio būseną, taip pat gauti galutinį lizdo ryšį. „SocketThread.isConnected“ () metodas pateikia loginę vertę, nurodančią, ar ryšys užmegztas, ir „SocketThread.getSocket“ () metodas grąžina a Lizdas. Panašūs metodai pateikiami norint nustatyti, ar įvyko klaida, ir norint pasiekti sugautą išimtį.

Visi šie metodai suteikia valdomą sąsają su „SocketThread“ pvz., neleidžiant išoriškai modifikuoti privačių narių kintamųjų. Šis kodo pavyzdys rodo gijos paleisti () metodas. Kada ir jei lizdo konstruktorius grąžina a Lizdas, jis bus priskirtas privataus nario kintamajam, prie kurio prieigos suteikia prieigos metodai. Kitą kartą paklausus ryšio būsenos, naudokite „SocketThread.isConnected“ () metodu, lizdą bus galima naudoti. Ta pati technika naudojama klaidoms aptikti; jeigu java.io.IOException yra sugautas, jis bus saugomas privačiame naryje, prie kurio galima patekti per „isError“ () ir „getException“ () prieigos metodai.