Programmering

Enkel håndtering av tidsavbrudd i nettverket

Mange programmerere gruer seg til tanken på å håndtere nettverkstidsavbrudd. En vanlig frykt er at en enkel nettverksklient med en tråd uten tidsavbruddsstøtte vil flyte inn i et komplekst flertrådet mareritt, med separate tråder som trengs for å oppdage nettverkstidsavbrudd, og en eller annen form for varslingsprosess på jobben mellom den blokkerte tråden og hovedapplikasjonen. Selv om dette er ett alternativ for utviklere, er det ikke det eneste. Å takle tidsavbrudd i nettverket trenger ikke være en vanskelig oppgave, og i mange tilfeller kan du helt unngå å skrive kode for flere tråder.

Når du arbeider med nettverkstilkoblinger eller en hvilken som helst type I / O-enhet, er det to klassifiseringer av operasjoner:

  • Blokkerer operasjoner: Les eller skriv boder, betjening venter til I / O-enheten er klar
  • Ikke-blokkerende operasjoner: Lese- eller skriveforsøk er gjort, operasjonen avbrytes hvis I / O-enheten ikke er klar

Java-nettverk er som standard en form for blokkering av I / O. Når et Java-nettverksapplikasjon leser fra en stikkontakt, vil det vanligvis vente på ubestemt tid hvis det ikke er noe øyeblikkelig svar. Hvis ingen data er tilgjengelig, vil programmet fortsette å vente, og det kan ikke gjøres noe videre arbeid. En løsning, som løser problemet, men introduserer litt ekstra kompleksitet, er å få en annen tråd til å utføre operasjonen; på denne måten, hvis den andre tråden blir blokkert, kan applikasjonen fortsatt svare på brukerkommandoer, eller til og med avslutte den fastlagte tråden om nødvendig.

Denne løsningen brukes ofte, men det er et mye enklere alternativ. Java støtter også ikke-blokkerende nettverk I / O, som kan aktiveres på alle Stikkontakt, ServerSocket, eller DatagramSocket. Det er mulig å spesifisere den maksimale tiden som en lese- eller skriveoperasjon vil stoppe før kontrollen returneres til applikasjonen. For nettverksklienter er dette den enkleste løsningen og tilbyr enklere og mer håndterbar kode.

Den eneste ulempen med ikke-blokkerende nettverk I / O under Java er at det krever en eksisterende kontakt. Selv om denne metoden er perfekt for normale lese- eller skriveoperasjoner, kan en tilkoblingsoperasjon stanse i en mye lengre periode, siden det ikke er noen metode for å spesifisere en tidsavbruddsperiode for tilkoblingsoperasjoner. Mange applikasjoner krever denne muligheten; Du kan imidlertid enkelt unngå det ekstra arbeidet med å skrive tilleggskode. Jeg har skrevet en liten klasse som lar deg spesifisere en tidsavbruddsverdi for en tilkobling. Den bruker en annen tråd, men de indre detaljene blir abstrahert bort. Denne tilnærmingen fungerer bra, siden den gir et ikke-blokkerende I / O-grensesnitt, og detaljene i den andre tråden er skjult for visning.

Ikke-blokkerende nettverk I / U

Den enkleste måten å gjøre noe på viser seg ofte å være den beste måten. Selv om det noen ganger er nødvendig å bruke tråder og blokkering av I / O, gir ikke-blokkerende I / O i de fleste tilfeller en langt klarere og mer elegant løsning. Med bare noen få kodelinjer kan du innlemme tidsavbruddsstøtter for alle sokkelapplikasjoner. Ikke tro meg? Les videre.

Da Java 1.1 ble utgitt, inkluderte den API-endringer i java.net pakke som tillot programmerere å spesifisere sokkelalternativer. Disse alternativene gir programmerere større kontroll over kontakten. Spesielt ett alternativ, SO_TIMEOUT, er ekstremt nyttig, fordi det lar programmerere spesifisere hvor lang tid en leseoperasjon vil blokkere. Vi kan spesifisere en kort forsinkelse, eller ingen i det hele tatt, og gjøre nettverkskoden vår blokkerende.

La oss se på hvordan dette fungerer. En ny metode, setSoTimeout (int) er lagt til følgende stikkontaktklasser:

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

Denne metoden lar oss spesifisere en maksimal tidsavbruddslengde, i millisekunder, som følgende nettverksoperasjoner vil blokkere:

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

Når en av disse metodene kalles, begynner klokken å tikke. Hvis operasjonen ikke er blokkert, vil den tilbakestilles og bare starte på nytt når en av disse metodene er kalt igjen; Som et resultat kan ingen tidsavbrudd oppstå med mindre du utfører en nettverks I / O-operasjon. Følgende eksempel viser hvor enkelt det kan være å håndtere tidsavbrudd, uten å bruke flere tråder for utførelse:

// Opprett en datagram-sokkel på port 2000 for å lytte etter innkommende UDP-pakker DatagramSocket dgramSocket = ny DatagramSocket (2000); // Deaktiver blokkering av I / O-operasjoner, ved å spesifisere en fem sekunders tidsavbrudd dgramSocket.setSoTimeout (5000); 

Å tilordne en tidsavbruddsverdi forhindrer at nettverksoperasjonen blokkeres på ubestemt tid. På dette punktet lurer du sannsynligvis på hva som vil skje når en nettverksoperasjon går ut. I stedet for å returnere en feilkode, som kanskje ikke alltid blir sjekket av utviklere, a java.io.InterruptedIOException blir kastet. Unntakshåndtering er en utmerket måte å håndtere feilforhold på, og lar oss skille vår normale kode fra vår feilhåndteringskode. Dessuten, hvem sjekker religiøst hver returverdi for en nullreferanse? Ved å kaste et unntak blir utviklere tvunget til å gi en fangstbehandler for tidsavbrudd.

Følgende kodebit viser hvordan du håndterer en timeout-operasjon når du leser fra en TCP-kontakt:

// Still sokkeltidsavbrudd for ti sekunders tilkobling. SetSoTimeout (10000); prøv {// Opprett en DataInputStream for lesing fra socket DataInputStream din = new DataInputStream (connection.getInputStream ()); // Les data til slutten av data for (;;) {Strenglinje = din.readLine (); hvis (linje! = null) System.out.println (linje); annet bryte; }} // Unntak kastet når nettverkstidsavbrudd oppstår fangst (InterruptedIOException iioe) {System.err.println ("Remote host time out under read operation"); } // Unntak kastet når generell nettverks I / O-feil oppstår fangst (IOException ioe) {System.err.println ("Nettverk I / O-feil -" + ioe); } 

Med bare noen få ekstra kodelinjer for en prøv {} fangstblokk, er det ekstremt enkelt å fange tidsavbrudd i nettverket. En applikasjon kan deretter svare på situasjonen uten å stoppe seg selv. For eksempel kan det starte med å varsle brukeren, eller ved å prøve å opprette en ny forbindelse. Når du bruker datagramkontakter, som sender pakker med informasjon uten å garantere levering, kan et program svare på et tidsavbrudd i nettverket ved å sende en pakke som hadde gått tapt under transport. Å implementere denne tidsavbruddsstøtten tar veldig lite tid og fører til en veldig ren løsning. Den eneste gangen at ikke-blokkerende I / O ikke er den optimale løsningen, er når du også trenger å oppdage tidsavbrudd ved tilkoblingsoperasjoner, eller når målmiljøet ditt ikke støtter Java 1.1.

Tidsavbruddshåndtering på tilkoblingsoperasjoner

Hvis målet ditt er å oppnå fullstendig deteksjon og håndtering av tidsavbrudd, må du vurdere tilkoblingsoperasjoner. Når du oppretter en forekomst av java.net.Socket, blir et forsøk på å opprette en forbindelse. Hvis vertsmaskinen er aktiv, men ingen tjenester kjører på porten som er spesifisert i java.net.Socket konstruktør, en ConnectionException vil bli kastet og kontrollen kommer tilbake til applikasjonen. Imidlertid, hvis maskinen er nede, eller hvis det ikke er noen rute til den verten, vil stikkontakten til slutt tid ut av seg selv mye senere. I mellomtiden forblir søknaden din frossen, og det er ingen måte å endre tidsavbruddsverdien.

Selv om sokkelkonstruktøranropet til slutt vil komme tilbake, innfører det en betydelig forsinkelse. En måte å håndtere dette problemet på er å bruke en annen tråd som vil utføre den potensielt blokkerende tilkoblingen, og å kontinuerlig avstemme den tråden for å se om en forbindelse er opprettet.

Dette fører imidlertid ikke alltid til en elegant løsning. Ja, du kan konvertere nettverksklientene dine til flertrådede applikasjoner, men ofte er mengden ekstra arbeid som kreves for å gjøre dette uoverkommelig. Det gjør koden mer kompleks, og når du bare skriver en enkel nettverksapplikasjon, er det vanskelig å rettferdiggjøre den innsatsen som kreves. Hvis du skriver mange nettverksapplikasjoner, vil du finne på å finne opp hjulet ofte. Det er imidlertid en enklere løsning.

Jeg har skrevet en enkel, gjenbrukbar klasse som du kan bruke i dine egne applikasjoner. Klassen genererer en TCP-stikkontakt uten å stoppe i lange tidsperioder. Du kaller ganske enkelt a getSocket metode, angi vertsnavn, port og tidsavbruddsforsinkelse, og motta en stikkontakt. Følgende eksempel viser en tilkoblingsforespørsel:

// Koble til en ekstern server etter vertsnavn, med en fire sekunders timeout Socket-tilkobling = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Hvis alt går bra, vil en stikkontakt bli returnert, akkurat som standarden java.net.Socket konstruktører. Hvis forbindelsen ikke kan opprettes før den angitte tidsavbruddet oppstår, vil metoden stoppe og kaste en java.io.InterruptedIOException, akkurat som andre sokkellesoperasjoner ville gjort når en tidsavbrudd er spesifisert ved hjelp av a setSoTimeout metode. Ganske enkelt, ikke sant?

Innkapsling av flertrådet nettverkskode i en enkelt klasse

Mens TimedSocket klasse er en nyttig komponent i seg selv, det er også et veldig bra læringshjelpemiddel for å forstå hvordan man skal håndtere blokkering av I / O. Når en blokkeringsoperasjon utføres, blir en applikasjon med en tråd blokkert på ubestemt tid. Hvis det brukes flere utførelsestråder, trenger bare en tråd stall; den andre tråden kan fortsette å kjøre. La oss ta en titt på hvordan TimedSocket klassen fungerer.

Når et program må koble til en ekstern server, påkaller det TimedSocket.getSocket () metode og sender detaljer om den eksterne verten og porten. De getSocket () metoden er overbelastet, slik at begge a String vertsnavn og et InetAddress som skal spesifiseres. Denne rekke parametere bør være tilstrekkelig for de fleste sokkeloperasjoner, selv om tilpasset overbelastning kan legges til for spesielle implementeringer. Inne i getSocket () metode, blir en annen tråd opprettet.

Den fantasifullt navngitte SocketTråd vil opprette en forekomst av java.net.Socket, som potensielt kan blokkere i betydelig tid. Det gir tilgangsmetoder for å bestemme om en forbindelse er opprettet, eller om det har oppstått en feil (for eksempel hvis java.net.SocketException ble kastet under tilkoblingen).

Mens tilkoblingen er opprettet, venter primærtråden til en forbindelse er opprettet, på at det oppstår en feil eller på et nettverkstidsavbrudd. Hvert hundre millisekund kontrolleres det for å se om den andre tråden har oppnådd en forbindelse. Hvis denne kontrollen mislykkes, må det foretas en ny kontroll for å avgjøre om det oppstod en feil i forbindelsen. Hvis ikke, og forbindelsesforsøket fortsatt fortsetter, økes en tidtaker, og etter en liten søvn vil forbindelsen bli undersøkt igjen.

Denne metoden bruker tung unntakshåndtering. Hvis det oppstår en feil, vil dette unntaket bli lest fra SocketTråd eksempel, og det vil bli kastet igjen. Hvis et nettverkstimeout oppstår, vil metoden kaste a java.io.InterruptedIOException.

Følgende kodebit viser avstemningsmekanismen og feilhåndteringskoden.

for (;;) {// Sjekk for å se om en forbindelse er opprettet hvis (st.isConnected ()) {// Ja ... tilordne sokkevariabel, og bryte ut av loop sock = st.getSocket (); gå i stykker; } annet {// Sjekk for å se om det oppstod en feil hvis (st.isError ()) {// Ingen forbindelse kunne opprettes throw (st.getException ()); } prøv {// Sov i en kort periode Thread.sleep (POLL_DELAY); } fangst (InterruptedException ie) {} // Intrement timer timer + = POLL_DELAY; // Sjekk for å se om tidsgrensen overskredes hvis (tidtaker> forsinkelse) {// Kan ikke koble til server kaste nye InterruptedIOException ("Kunne ikke koble til" + forsinkelse + "millisekunder") }}} 

Inne i den blokkerte tråden

Mens forbindelsen regelmessig blir undersøkt, prøver den andre tråden å opprette en ny forekomst av java.net.Socket. Tilbehørsmetoder er gitt for å bestemme tilstanden til forbindelsen, samt for å få den endelige kontakten. De SocketThread.isConnected () metoden returnerer en boolsk verdi for å indikere om en forbindelse er opprettet, og SocketThread.getSocket () metoden returnerer a Stikkontakt. Lignende metoder er gitt for å avgjøre om en feil har oppstått, og for å få tilgang til unntaket som ble fanget.

Alle disse metodene gir et kontrollert grensesnitt til SocketTråd for eksempel uten å tillate ekstern endring av private medlemsvariabler. Følgende kodeeksempel viser trådens løpe() metode. Når, og hvis, sokkelkonstruktøren returnerer a Stikkontakt, vil den bli tildelt en privat medlemsvariabel, som tilgangsmetodene gir tilgang til. Neste gang det blir spurt om en tilkoblingstilstand, bruker du SocketThread.isConnected () metode, vil kontakten være tilgjengelig for bruk. Den samme teknikken brukes til å oppdage feil; hvis en java.io.IO Unntak blir fanget, lagres den i et privat medlem, som er tilgjengelig via isError () og getException () tilgangsmetoder.

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