Programmering

Enkel håndtering af netværks timeouts

Mange programmører frygter tanken om at håndtere netværkstimeouts. En almindelig frygt er, at en enkel enkelttrådsnetværksklient uden timeout-support vil ballonere ind i et komplekst flertrådet mareridt med separate tråde, der er nødvendige for at opdage netværks timeouts, og en eller anden form for underretningsproces på arbejdspladsen mellem den blokerede tråd og hovedapplikationen. Selvom dette er en mulighed for udviklere, er det ikke den eneste. At håndtere timeouts på netværket behøver ikke være en vanskelig opgave, og i mange tilfælde kan du helt undgå at skrive kode til yderligere tråde.

Når du arbejder med netværksforbindelser eller en hvilken som helst type I / O-enhed, er der to klassifikationer af operationer:

  • Blokering af operationer: Læs eller skriv båse, betjening venter, indtil I / O-enheden er klar
  • Ikke-blokerende operationer: Læse- eller skriveforsøg foretages, betjening afbrydes, hvis I / O-enheden ikke er klar

Java-netværk er som standard en form for blokering af I / O. Når et Java-netværksapplikation læser fra en stikketforbindelse, vil det således normalt vente på ubestemt tid, hvis der ikke er noget øjeblikkeligt svar. Hvis der ikke findes data, venter programmet, og der kan ikke udføres yderligere arbejde. En løsning, der løser problemet, men introducerer lidt ekstra kompleksitet, er at få en anden tråd til at udføre operationen; på denne måde, hvis den anden tråd bliver blokeret, kan applikationen stadig svare på brugerkommandoer eller endda afslutte den stoppede tråd, hvis det er nødvendigt.

Denne løsning anvendes ofte, men der er et meget enklere alternativ. Java understøtter også ikke-blokerende netværk I / O, som kan aktiveres på alle Stikkontakt, ServerSocket, eller DatagramSocket. Det er muligt at specificere den maksimale tid, som en læse- eller skriveoperation vil stoppe, før kontrollen returneres til applikationen. For netværksklienter er dette den nemmeste løsning og tilbyder enklere og mere håndterbar kode.

Den eneste ulempe ved ikke-blokerende netværk I / O under Java er, at det kræver et eksisterende stik. Selvom denne metode således er perfekt til normale læse- eller skriveoperationer, kan en tilslutningsoperation stoppe i en meget længere periode, da der ikke er nogen metode til at specificere en timeout-periode for tilslutningsoperationer. Mange applikationer kræver denne evne; Du kan dog let undgå det ekstra arbejde med at skrive ekstra kode. Jeg har skrevet en lille klasse, der giver dig mulighed for at specificere en timeout-værdi for en forbindelse. Det bruger en anden tråd, men de interne detaljer er abstraheret væk. Denne tilgang fungerer godt, da den giver en ikke-blokerende I / O-grænseflade, og detaljerne i den anden tråd er skjult.

Ikke-blokerende netværk I / O

Den enkleste måde at gøre noget på viser sig ofte at være den bedste måde. Mens det undertiden er nødvendigt at bruge tråde og blokering af I / O, er ikke-blokerende I / O i de fleste tilfælde en langt klarere og mere elegant løsning. Med kun et par linjer med kode kan du inkorporere timeout-understøttelser til enhver socket-applikation. Tro mig ikke? Læs videre.

Da Java 1.1 blev frigivet, inkluderede det API-ændringer til java.net pakke, der tillod programmører at specificere sokkelindstillinger. Disse muligheder giver programmører større kontrol over sokkelkommunikation. Især en mulighed, SO_TIMEOUT, er yderst nyttigt, fordi det giver programmører mulighed for at specificere, hvor lang tid en læsning vil blokere. Vi kan angive en kort forsinkelse eller slet ingen og gøre vores netværkskode blokerende.

Lad os se på, hvordan dette fungerer. En ny metode, setSoTimeout (int) er blevet føjet til følgende sokkelklasser:

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

Denne metode giver os mulighed for at specificere en maksimal timeout-længde i millisekunder, som følgende netværkshandlinger vil blokere:

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

Når en af ​​disse metoder kaldes, begynder uret at tikke. Hvis operationen ikke er blokeret, nulstilles den og genstarter først, når en af ​​disse metoder kaldes op igen; som et resultat kan der aldrig forekomme timeout, medmindre du udfører en netværks-I / O-handling. Følgende eksempel viser, hvor let det kan være at håndtere timeouts uden at ty til flere udførelsestråde:

// Opret en datagram-sokkel på port 2000 for at lytte efter indgående UDP-pakker DatagramSocket dgramSocket = ny DatagramSocket (2000); // Deaktiver blokering af I / O-operationer ved at angive en fem sekunders timeout dgramSocket.setSoTimeout (5000); 

Tildeling af en timeout-værdi forhindrer vores netværkshandlinger i at blokere på ubestemt tid. På dette tidspunkt spekulerer du sandsynligvis på, hvad der vil ske, når en netværksdrift timeout. I stedet for at returnere en fejlkode, som måske ikke altid kontrolleres af udviklere, a java.io.InterruptedIOException kastes. Undtagelseshåndtering er en glimrende måde at håndtere fejlforhold på og giver os mulighed for at adskille vores normale kode fra vores fejlhåndteringskode. Desuden, hvem kontrollerer religiøst hver returværdi for en null reference? Ved at kaste en undtagelse er udviklere tvunget til at give en fangsthåndterer til timeouts.

Følgende kodestykke viser, hvordan man håndterer en timeout-operation, når man læser fra et TCP-stik:

// Indstil stikkontakt-timeout til tilslutning af ti sekunder. SetSoTimeout (10000); prøv {// Opret en DataInputStream til læsning fra sokkel DataInputStream din = ny DataInputStream (connection.getInputStream ()); // Læs data indtil slutningen af ​​data for (;;) {String line = din.readLine (); hvis (linje! = null) System.out.println (linje); andet bryde; }} // Undtagelse kastet, når netværkets timeout forekommer fangst (InterruptedIOException iioe) {System.err.println ("Fjernværten blev timeout under læsning"); } // Undtagelse kastet, når generel netværks I / O-fejl opstår fangst (IOException ioe) {System.err.println ("Netværks I / O-fejl -" + ioe); } 

Med kun få ekstra kodelinjer til en prøve {} fangstblok, det er ekstremt let at fange netværkstimeouts. En applikation kan derefter reagere på situationen uden at stoppe sig selv. For eksempel kan det starte med at underrette brugeren eller ved at forsøge at oprette en ny forbindelse. Når du bruger datagram-stikkontakter, der sender pakker med information uden at garantere levering, kan en applikation reagere på et netværks-timeout ved at sende en pakke, der var gået tabt under forsendelse. Implementering af denne timeout support tager meget lidt tid og fører til en meget ren løsning. Faktisk er den eneste gang, at ikke-blokerende I / O ikke er den optimale løsning, når du også har brug for at opdage timeouts ved tilslutningsoperationer, eller når dit målmiljø ikke understøtter Java 1.1.

Timeout-håndtering af forbindelsesoperationer

Hvis dit mål er at opnå fuldstændig timeout-detektion og -håndtering, skal du overveje at forbinde operationer. Når du opretter en forekomst af java.net.Socket, er der forsøgt at etablere en forbindelse. Hvis værtsmaskinen er aktiv, men der ikke kører nogen service på den port, der er angivet i java.net.Socket konstruktør, en ConnectionException bliver kastet, og kontrol vender tilbage til applikationen. Men hvis maskinen er nede, eller hvis der ikke er nogen rute til den vært, vil stikkontakten til sidst afslutte sig selv meget senere. I mellemtiden forbliver din applikation frossen, og der er ingen måde at ændre timeoutværdien på.

Selvom sokkelkonstruktøropkaldet til sidst vender tilbage, indfører det en betydelig forsinkelse. En måde at håndtere dette problem på er at anvende en anden tråd, som udfører den potentielt blokerende forbindelse, og til løbende at afstemme den tråd for at se, om der er oprettet en forbindelse.

Dette fører dog ikke altid til en elegant løsning. Ja, du kan konvertere dine netværksklienter til multitrådede applikationer, men ofte er mængden af ​​ekstra arbejde, der kræves for at gøre dette, uoverkommelig. Det gør koden mere kompleks, og når man kun skriver en simpel netværksapplikation, er den krævede indsats vanskelig at retfærdiggøre. Hvis du skriver mange netværksapplikationer, vil du finde ud af, at du ofte genopfinder hjulet. Der er dog en enklere løsning.

Jeg har skrevet en enkel, genanvendelig klasse, som du kan bruge i dine egne applikationer. Klassen genererer en TCP-stikforbindelse uden at gå i stå i lange perioder. Du kalder simpelthen en getSocket metode, angive værtsnavn, port og forsinkelse af timeout og modtage en stikkontakt. Følgende eksempel viser en forbindelsesanmodning:

// Opret forbindelse til en ekstern server efter værtsnavn med en fire sekunders timeout Socket-forbindelse = TimedSocket.getSocket ("server.my-network.net", 23, 4000); 

Hvis alt går godt, returneres en stikkontakt, ligesom standarden java.net.Socket konstruktører. Hvis forbindelsen ikke kan oprettes, før din angivne timeout finder sted, stopper metoden og kaster et java.io.InterruptedIOException, ligesom andre socket-read-operationer ville, når en timeout er specificeret ved hjælp af a setSoTimeout metode. Temmelig let, hva '?

Indkapsling af multitrådet netværkskode i en enkelt klasse

Mens TimedSocket klasse er en nyttig komponent i sig selv, det er også en meget god læringshjælp til at forstå, hvordan man håndterer blokering af I / O. Når en blokering udføres, blokeres en applikation med en enkelt gevind på ubestemt tid. Hvis der anvendes flere udførelsestråde, behøver kun en tråd stall; den anden tråd kan fortsætte med at udføre. Lad os se på, hvordan TimedSocket klasse fungerer.

Når et program skal oprette forbindelse til en ekstern server, påberåber det sig TimedSocket.getSocket () metode og videregiver oplysninger om fjernværten og porten. Det getSocket () metoden er overbelastet, så begge kan Snor værtsnavn og et InetAddress skal specificeres. Denne række af parametre skal være tilstrækkelig til de fleste socket-operationer, selvom tilpasset overbelastning kan tilføjes til specielle implementeringer. Inde i getSocket () metode oprettes en anden tråd.

Den fantasifuldt navngivne SocketTråd opretter en forekomst af java.net.Socket, som potentielt kan blokere i lang tid. Det giver adgangsmetoder til at bestemme, om en forbindelse er oprettet, eller om der er opstået en fejl (for eksempel hvis java.net.SocketException blev kastet under tilslutningen).

Mens forbindelsen oprettes, venter den primære tråd, indtil der oprettes en forbindelse, for der opstår en fejl eller for et netværks-timeout. Hvert hundrede millisekund kontrolleres, om den anden tråd har opnået en forbindelse. Hvis denne kontrol mislykkes, skal der foretages en anden kontrol for at afgøre, om der opstod en fejl i forbindelsen. Hvis ikke, og forbindelsesforsøget fortsætter, øges en timer, og efter en lille slumring vil forbindelsen blive pollet igen.

Denne metode gør tung brug af undtagelseshåndtering. Hvis der opstår en fejl, læses denne undtagelse fra SocketTråd eksempel, og det vil blive kastet igen. Hvis der opstår et netværkstimeout, kaster metoden a java.io.InterruptedIOException.

Følgende kodestykke viser afstemningsmekanismen og fejlhåndteringskoden.

for (;;) {// Kontroller, om der er oprettet en forbindelse, hvis (st.isConnected ()) {// Ja ... tildel til sokvariabel, og bryd ud af loop sock = st.getSocket (); pause; } andet {// Kontroller for at se, om der opstod en fejl, hvis (st.isError ()) {// Ingen forbindelse kunne oprettes throw (st.getException ()); } prøv {// Sleep i en kort periode Thread.sleep (POLL_DELAY); } fange (InterruptedException ie) {} // Increment timer timer + = POLL_DELAY; // Kontroller, om tidsgrænsen er overskredet, hvis (timer> forsinkelse) {// Kan ikke oprette forbindelse til serveren, smid nyt InterruptedIOException ("Kunne ikke oprette forbindelse til" + forsinkelse + "millisekunder"); }}} 

Inde i den blokerede tråd

Mens forbindelsen regelmæssigt polles, forsøger den anden tråd at oprette en ny forekomst af java.net.Socket. Accessor-metoder tilvejebringes for at bestemme forbindelsens tilstand samt for at få den endelige stikket-forbindelse. Det SocketThread.isConnected () metoden returnerer en boolsk værdi for at angive, om en forbindelse er oprettet, og SocketThread.getSocket () metode returnerer a Stikkontakt. Lignende metoder leveres til at bestemme, om der er opstået en fejl og få adgang til den undtagelse, der blev fanget.

Alle disse metoder giver en kontrolleret grænseflade til SocketTråd eksempel uden at tillade ekstern ændring af private medlemsvariabler. Følgende kodeeksempel viser trådens løb() metode. Hvornår og hvis sokkelkonstruktøren returnerer a Stikkontakt, vil den blive tildelt en privat medlemsvariabel, hvortil adgangsmetoderne giver adgang. Næste gang en forbindelsestilstand forespørges ved hjælp af SocketThread.isConnected () metode, vil stikkontakten være tilgængelig til brug. Den samme teknik bruges til at opdage fejl; hvis en java.io.IOUndtagelse er fanget, gemmes det i et privat medlem, som kan tilgås via isError () og getException () tilgangsmetoder.