Introduktion til Java NIO Selector

1. Oversigt

I denne artikel vil vi udforske de indledende dele af Java NIO'er Vælger komponent.

En vælger tilvejebringer en mekanisme til overvågning af en eller flere NIO-kanaler og genkendelse af, hvornår en eller flere bliver tilgængelige til dataoverførsel.

Denne måde, en enkelt tråd kan bruges til styring af flere kanalerog dermed flere netværksforbindelser.

2. Hvorfor bruge en vælger?

Med en vælger kan vi bruge en tråd i stedet for flere til at styre flere kanaler. Det er dyrt for operativsystemet at skifte kontekst mellem trådeog derudover hver tråd optager hukommelse.

Derfor, jo færre tråde vi bruger, jo bedre. Det er dog vigtigt at huske det moderne operativsystemer og CPU'er bliver bedre ved multitasking, så omkostningerne ved multi-threading bliver mindre med tiden.

Vi skal beskæftige os med her, hvordan vi kan håndtere flere kanaler med en enkelt tråd ved hjælp af en vælger.

Bemærk også, at vælgere ikke kun hjælper dig med at læse data; de kan også lytte efter indgående netværksforbindelser og skrive data på tværs af langsomme kanaler.

3. Opsætning

For at bruge vælgeren behøver vi ingen speciel opsætning. Alle de klasser, vi har brug for, er kernen java.nio pakke, og vi skal bare importere det, vi har brug for.

Derefter kan vi registrere flere kanaler med et vælgerobjekt. Når I / O-aktivitet sker på en af ​​kanalerne, giver vælgeren os besked. Sådan kan vi læse fra et stort antal datakilder fra en enkelt tråd.

Enhver kanal, vi registrerer hos en vælger, skal være en underklasse af Valgbar kanal. Dette er en særlig type kanaler, der kan sættes i ikke-blokerende tilstand.

4. Oprettelse af en vælger

En vælger kan oprettes ved at påkalde det statiske åben metode til Vælger klasse, som bruger systemets standardudbyder til at oprette en ny vælger:

Selector selector = Selector.open ();

5. Registrering af valgbare kanaler

For at en vælger skal overvåge kanaler, skal vi registrere disse kanaler hos vælgeren. Vi gør dette ved at påberåbe os Tilmeld metode til den valgbare kanal.

Men før en kanal er registreret med en vælger, skal den være i ikke-blokeringstilstand:

channel.configureBlocking (false); SelectionKey-tast = channel.register (selector, SelectionKey.OP_READ);

Det betyder, at vi ikke kan bruge FileChannels med en vælger, da de ikke kan skiftes til ikke-blokerende tilstand, som vi gør med sokkelkanaler.

Den første parameter er Vælger objekt, vi oprettede tidligere, definerer den anden parameter et interessesæt, hvilket betyder hvilke begivenheder vi er interesseret i at lytte til i den overvågede kanal via vælgeren.

Der er fire forskellige begivenheder, vi kan lytte til, hver er repræsenteret af en konstant i SelectionKey klasse:

  • Opret forbindelse når en klient forsøger at oprette forbindelse til serveren. Repræsenteret af SelectionKey.OP_CONNECT
  • Acceptere når serveren accepterer en forbindelse fra en klient. Repræsenteret af SelectionKey.OP_ACCEPT
  • Læs når serveren er klar til at læse fra kanalen. Repræsenteret af SelectionKey.OP_READ
  • Skrive når serveren er klar til at skrive til kanalen. Repræsenteret af SelectionKey.OP_WRITE

Det returnerede objekt SelectionKey repræsenterer den valgbare kanals registrering hos vælgeren. Vi ser nærmere på det i det følgende afsnit.

6. Den SelectionKey Objekt

Som vi så i det foregående afsnit, når vi registrerer en kanal med en vælger, får vi en SelectionKey objekt. Dette objekt indeholder data, der repræsenterer registreringen af ​​kanalen.

Det indeholder nogle vigtige egenskaber, som vi skal forstå godt for at kunne bruge vælgeren på kanalen. Vi ser på disse egenskaber i de følgende underafsnit.

6.1. Rentesættet

Et interessesæt definerer det sæt begivenheder, som vi ønsker, at vælgeren skal passe på på denne kanal. Det er en heltalværdi; vi kan få disse oplysninger på følgende måde.

For det første har vi den interesse, der er returneret af SelectionKey'S interestOps metode. Så har vi begivenheden konstant i SelectionKey vi kiggede på tidligere.

Når vi OG disse to værdier, får vi en boolsk værdi, der fortæller os, om begivenheden overvåges eller ej:

int interestSet = selectionKey.interestOps (); boolsk isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; boolsk isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolsk isInterestedInRead = interestSet & SelectionKey.OP_READ; boolsk isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

6.2. Det klare sæt

Klar sæt definerer det sæt begivenheder, som kanalen er klar til. Det er også et heltal; vi kan få disse oplysninger på følgende måde.

Vi har fået det klare sæt returneret af SelectionKey'S readyOps metode. Når vi OG denne værdi med begivenhedskonstanterne, som vi gjorde i tilfælde af interesse, får vi en boolsk repræsentant for, om kanalen er klar til en bestemt værdi eller ej.

Et andet alternativ og kortere måde at gøre dette på er at bruge Valgnøgle 's bekvemmelighed metoder til dette samme formål:

selectionKey.isAcceptable (); selectionKey.isConnectable (); selectionKey.isReadable (); selectionKey.isWriteable ();

6.3. Kanalen

Adgang til den kanal, der overvåges fra SelectionKey objektet er meget simpelt. Vi kalder bare kanal metode:

Kanal kanal = key.channel ();

6.4. Vælgeren

Ligesom at få en kanal er det meget let at få den Vælger objekt fra SelectionKey objekt:

Vælgervælger = key.selector ();

6.5. Vedhæftning af genstande

Vi kan vedhæfte en genstand til en SelectionKey. Nogle gange vil vi måske give en kanal et brugerdefineret ID eller vedhæfte enhver form for Java-objekt, som vi måske vil holde styr på.

Vedhæftning af objekter er en praktisk måde at gøre det på. Sådan vedhæftes og hentes objekter fra en SelectionKey:

key.attach (Objekt); Objektobjekt = key.attachment ();

Alternativt kan vi vælge at vedhæfte et objekt under kanalregistrering. Vi tilføjer det som en tredje parameter til kanalerne Tilmeld metode som sådan:

SelectionKey nøgle = channel.register (selector, SelectionKey.OP_ACCEPT, objekt);

7. Valg af kanaltast

Indtil videre har vi set på, hvordan man opretter en vælger, registrerer kanaler til den og inspicerer egenskaberne for SelectionKey objekt, der repræsenterer en kanals registrering til en vælger.

Dette er kun halvdelen af ​​processen, nu skal vi udføre en kontinuerlig proces med at vælge det klare sæt, som vi kiggede på tidligere. Vi udvælger ved hjælp af vælgerens Vælg metode som sådan:

int kanaler = selector.select ();

Denne metode blokerer, indtil mindst en kanal er klar til en operation. Det returnerede heltal repræsenterer antallet af nøgler, hvis kanaler er klar til en operation.

Dernæst henter vi normalt sættet med valgte nøgler til behandling:

Indstil valgteKeys = selector.selectedKeys ();

Det sæt, vi har fået, er af SelectionKey objekter, repræsenterer hver nøgle en registreret kanal, der er klar til en operation.

Herefter gentager vi normalt dette sæt, og for hver nøgle opnår vi kanalen og udfører de operationer, der vises i vores interesse, der er sat på den.

I løbet af en kanals levetid kan den vælges flere gange, da dens nøgle vises i det klare sæt til forskellige begivenheder. Dette er grunden til, at vi skal have en kontinuerlig sløjfe for at registrere og behandle kanalhændelser, når og når de opstår.

8. Komplet eksempel

For at cementere den viden, vi har fået i de foregående afsnit, skal vi opbygge et komplet klientservereksempel.

For at lette testningen af ​​vores kode opbygger vi en ekkoserver og en ekkoklient. I denne form for opsætning opretter klienten forbindelse til serveren og begynder at sende meddelelser til den. Serveren gentager beskeder sendt af hver klient.

Når serveren støder på en bestemt besked, f.eks ende, det fortolker det som afslutningen på kommunikationen og lukker forbindelsen med klienten.

8.1. Serveren

Her er vores kode til EchoServer.java:

offentlig klasse EchoServer {privat statisk endelig String POISON_PILL = "POISON_PILL"; offentlig statisk ugyldig hoved (String [] args) kaster IOException {Selector selector = Selector.open (); ServerSocketChannel serverSocket = ServerSocketChannel.open (); serverSocket.bind (ny InetSocketAddress ("localhost", 5454)); serverSocket.configureBlocking (false); serverSocket.register (vælger, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate (256); while (true) {selector.select (); Indstil valgteKeys = selector.selectedKeys (); Iterator iter = valgtKeys.iterator (); mens (iter.hasNext ()) {SelectionKey-nøgle = iter.next (); hvis (key.isAcceptable ()) {register (selector, serverSocket); } hvis (key.isReadable ()) {answerWithEcho (buffer, nøgle); } iter.remove (); }}} privat statisk ugyldigt svarWithEcho (ByteBuffer-buffer, SelectionKey-tast) kaster IOException {SocketChannel-klient = (SocketChannel) key.channel (); client.read (buffer); hvis (ny streng (buffer.array ()). trim (). er lig med (POISON_PILL)) {client.close (); System.out.println ("Accepterer ikke længere klientmeddelelser"); } andet {buffer.flip (); client.write (buffer); buffer.clear (); }} privat statisk ugyldigt register (Selector selector, ServerSocketChannel serverSocket) kaster IOException {SocketChannel client = serverSocket.accept (); client.configureBlocking (false); client.register (vælger, SelectionKey.OP_READ); } offentlig statisk processtart () kaster IOException, InterruptedException {String javaHome = System.getProperty ("java.home"); String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; String classpath = System.getProperty ("java.class.path"); String className = EchoServer.class.getCanonicalName (); ProcessBuilder builder = ny ProcessBuilder (javaBin, "-cp", classpath, className); returner builder.start (); }}

Dette er hvad der sker; vi skaber en Vælger objekt ved at kalde det statiske åben metode. Vi opretter derefter en kanal også ved at kalde dens statiske åben metode, specifikt a ServerSocketChannel eksempel.

Dette er fordi ServerSocketChannel er valgbar og god til et streamorienteret lyttestik.

Vi binder det derefter til en havn efter eget valg. Husk, vi sagde tidligere, at inden vi registrerer en valgbar kanal til en vælger, skal vi først indstille den til ikke-blokerende tilstand. Så næste gør vi dette og registrerer derefter kanalen til vælgeren.

Vi har ikke brug for SelectionKey forekomst af denne kanal på dette stadium, så vi kan ikke huske det.

Java NIO bruger en anden bufferorienteret model end en streamorienteret model. Så sokkelkommunikation finder normalt sted ved at skrive til og læse fra en buffer.

Derfor skaber vi et nyt ByteBuffer som serveren skriver til og læser fra. Vi initialiserer det til 256 byte, det er bare en vilkårlig værdi, afhængigt af hvor meget data vi planlægger at overføre frem og tilbage.

Endelig udfører vi udvælgelsesprocessen. Vi vælger de klare kanaler, henter deres valgtaster, gentager dem over tasterne og udfører de operationer, som hver kanal er klar til.

Vi gør dette i en uendelig løkke, da servere normalt skal køre, uanset om der er en aktivitet eller ej.

Den eneste operation a ServerSocketChannel kan håndtere er en ACCEPTERE operation. Når vi accepterer forbindelsen fra en klient, får vi en SocketChannel objekt, som vi kan læse og skrive på. Vi indstiller den til ikke-blokerende tilstand og registrerer den til en LÆS-operation til vælgeren.

Under en af ​​de efterfølgende valg bliver denne nye kanal læseklar. Vi henter det og læser det ind i bufferen. Tro mod det er som en ekkoserver, vi skal skrive dette indhold tilbage til klienten.

Når vi ønsker at skrive til en buffer, hvorfra vi har læst, skal vi kalde flip () metode.

Vi indstiller endelig bufferen til skrivetilstand ved at ringe til flip metode og blot skrive til den.

Det Start() metode er defineret, så ekkoserveren kan startes som en separat proces under enhedstest.

8.2. Klienten

Her er vores kode til EchoClient.java:

offentlig klasse EchoClient {privat statisk SocketChannel-klient; privat statisk ByteBuffer-buffer; privat statisk EchoClient-forekomst; offentlig statisk EchoClient start () {if (forekomst == null) forekomst = ny EchoClient (); returinstans } offentligt statisk ugyldigt stop () kaster IOException {client.close (); buffer = null; } privat EchoClient () {prøv {client = SocketChannel.open (ny InetSocketAddress ("localhost", 5454)); buffer = ByteBuffer.allocate (256); } fange (IOException e) {e.printStackTrace (); }} offentlig String sendMessage (String msg) {buffer = ByteBuffer.wrap (msg.getBytes ()); Strengrespons = null; prøv {client.write (buffer); buffer.clear (); client.read (buffer); svar = ny streng (buffer.array ()). trim (); System.out.println ("respons =" ​​+ respons); buffer.clear (); } fange (IOException e) {e.printStackTrace (); } returnere svar }}

Klienten er enklere end serveren.

Vi bruger et enkelt mønster til at instantiere det inde i Start statisk metode. Vi kalder den private konstruktør fra denne metode.

I den private konstruktør åbner vi en forbindelse på den samme port, som serverkanalen var bundet til og stadig på den samme vært.

Vi opretter derefter en buffer, som vi kan skrive, og hvorfra vi kan læse.

Endelig har vi en Send besked metode, der læser, indpakker enhver streng, vi sender til den, i en bytebuffer, der transmitteres over kanalen til serveren.

Vi læser derefter fra klientkanalen for at få beskeden sendt af serveren. Vi returnerer dette som ekkoet af vores budskab.

8.3. Testning

Inde i en klasse kaldet EchoTest.java, skal vi oprette en testcase, der starter serveren, sender meddelelser til serveren og kun passerer, når de samme meddelelser modtages tilbage fra serveren. Som et sidste trin stopper testsagen serveren, inden den er færdig.

Vi kan nu køre testen:

offentlig klasse EchoTest {Processerver; EchoClient-klient; @Før offentlig ugyldig opsætning () kaster IOException, InterruptedException {server = EchoServer.start (); klient = EchoClient.start (); } @ Test offentligt ugyldigt givenServerClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hej"); Streng resp2 = client.sendMessage ("verden"); assertEquals ("hej", resp1); assertEquals ("verden", resp2); } @Efter offentlig ugyldig nedbrydning () kaster IOException {server.destroy (); EchoClient.stop (); }}

9. Konklusion

I denne artikel har vi dækket grundlæggende brug af Java NIO Selector-komponenten.

Den komplette kildekode og alle kodestykker til denne artikel er tilgængelige i mit GitHub-projekt.


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