Uvod u Java NIO Selector

1. Pregled

U ovom ćemo članku istražiti uvodne dijelove Java NIO-a Selektor komponenta.

Birač nudi mehanizam za nadgledanje jednog ili više NIO kanala i prepoznavanje kada jedan ili više postanu dostupni za prijenos podataka.

Ovuda, jedna nit se može koristiti za upravljanje više kanala, a time i više mrežnih veza.

2. Zašto koristiti selektor?

Pomoću selektora možemo koristiti jednu nit umjesto nekoliko za upravljanje više kanala. Prebacivanje konteksta između niti skupo je za operativni sustav, i dodatno, svaka nit zauzima memoriju.

Stoga, što manje niti koristimo, to bolje. Međutim, važno je to upamtiti suvremeni operativni sustavi i CPU postaju sve bolji u multitaskingu, tako da se troškovi višestrukog navoja s vremenom smanjuju.

Ovdje ćemo se pozabaviti načinom na koji pomoću selektora možemo upravljati s više kanala s jednom niti.

Također imajte na umu da vam selektori ne pomažu samo u čitanju podataka; oni također mogu slušati dolazne mrežne veze i pisati podatke preko sporih kanala.

3. Postavljanje

Da bismo koristili selektor, ne trebaju nam posebne postavke. Sva nastava koja nam treba je srž java.nio paket, a mi samo moramo uvesti ono što nam treba.

Nakon toga možemo registrirati više kanala pomoću objekta selektora. Kad se I / O aktivnost dogodi na bilo kojem kanalu, birač nas obavještava. Tako možemo čitati iz velikog broja izvora podataka iz jedne niti.

Bilo koji kanal koji registriramo selektorom mora biti podklasa Kanal koji se može odabrati. To su posebna vrsta kanala koji se mogu staviti u neblokirajući način.

4. Izrada selektora

Selektor se može stvoriti pozivanjem statičkog otvorena metoda Selektor klase, koja će upotrijebiti zadani davatelj usluga selektora za stvaranje novog selektora:

Selektor selektor = Selector.open ();

5. Registriranje odabirnih kanala

Da bi selektor mogao nadgledati bilo koji kanal, moramo te kanale registrirati kod selektora. To radimo pozivajući se na Registar metoda odabirnog kanala.

Ali prije nego što se kanal registrira pomoću selektora, mora biti u načinu neblokiranja:

channel.configureBlocking (netačno); Tipka SelectionKey = channel.register (selektor, SelectionKey.OP_READ);

To znači da ne možemo koristiti FileChannels pomoću selektora, jer ih nije moguće prebaciti u neblokirajući način kao što to radimo s socket kanalima.

Prvi parametar je Selektor objekt koji smo stvorili ranije, drugi parametar definira skup interesa, što znači koje smo događaje zainteresirani za slušanje u nadgledanom kanalu putem selektora.

Postoje četiri različita događaja koja možemo slušati, a svaki je predstavljen konstantom u Tipka za odabir razred:

  • Spojiti kada se klijent pokušava povezati s poslužiteljem. Predstavljen od SelectionKey.OP_CONNECT
  • Prihvatiti kad poslužitelj prihvati vezu od klijenta. Predstavljen od SelectionKey.OP_ACCEPT
  • Čitati kada je poslužitelj spreman za čitanje s kanala. Predstavljen od SelectionKey.OP_READ
  • Pisati kada je poslužitelj spreman za pisanje na kanal. Predstavljen od SelectionKey.OP_WRITE

Vraćeni objekt Tipka za odabir predstavlja registraciju selekcijskog kanala s selektorom. Dalje ćemo ga pogledati u sljedećem odjeljku.

6. The Tipka za odabir Objekt

Kao što smo vidjeli u prethodnom odjeljku, kada registriramo kanal selektorom, dobivamo a Tipka za odabir objekt. Ovaj objekt sadrži podatke koji predstavljaju registraciju kanala.

Sadrži neka važna svojstva koja moramo dobro razumjeti da bismo mogli koristiti selektor na kanalu. Ta ćemo svojstva pogledati u sljedećim pododjeljcima.

6.1. Skup interesa

Skup interesa definira skup događaja na koje želimo da selektor pripazi na ovom kanalu. To je cijela vrijednost; te podatke možemo dobiti na sljedeći način.

Prvo, skup kamata vraća Tipka za odabir‘S kamateOps metoda. Tada imamo konstantu događaja u Tipka za odabir gledali smo ranije.

Kada I i ove dvije vrijednosti dobijemo logičku vrijednost koja nam govori da li se događaj promatra ili ne:

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

6.2. Spremni set

Spremni set definira skup događaja za koje je kanal spreman. To je i cijela vrijednost; te podatke možemo dobiti na sljedeći način.

Dobili smo spremni set Tipka za odabir‘S readyOps metoda. Kada I ovu vrijednost s konstantama događaja kao što smo to učinili u slučaju skupa interesa, dobit ćemo logičku vrijednost koja predstavlja je li kanal spreman za određenu vrijednost ili ne.

Drugi alternativni i kraći način za to je korištenje OdabirKljuč 's praktične metode za istu svrhu:

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

6.3. Kanal

Pristup kanalu koji se gleda s Tipka za odabir objekt je vrlo jednostavan. Samo zovemo kanal metoda:

Kanal kanala = key.channel ();

6.4. Izbornik

Baš kao i dobivanje kanala, vrlo je lako dobiti Selektor objekt iz Tipka za odabir objekt:

Selektor selektor = key.selector ();

6.5. Pričvršćivanje predmeta

Možemo pričvrstiti objekt na Tipka za odabir. Ponekad ćemo možda htjeti dati kanalu prilagođeni ID ili priložiti bilo koju vrstu Java objekta koji bismo možda željeli pratiti.

Pričvršćivanje predmeta zgodan je način za to. Evo kako pričvršćujete i dohvaćate predmete iz a Tipka za odabir:

key.attach (Objekt); Objektni objekt = key.attachment ();

Alternativno, možemo odabrati pričvršćivanje objekta tijekom registracije kanala. Dodajemo ga kao treći parametar kanalima Registar metoda, ovako:

Tipka SelectionKey = channel.register (selektor, SelectionKey.OP_ACCEPT, objekt);

7. Odabir tipke kanala

Do sada smo gledali kako stvoriti selektor, registrirati kanale na njega i pregledati svojstva Tipka za odabir objekt koji predstavlja registraciju kanala za selektor.

Ovo je samo polovica procesa, sada moramo izvoditi kontinuirani postupak odabira spremnog seta koji smo gledali ranije. Odabir radimo pomoću selektora Odaberi metoda, ovako:

int kanali = selector.select ();

Ova metoda blokira dok barem jedan kanal nije spreman za operaciju. Vraćeni cijeli broj predstavlja broj tipki čiji su kanali spremni za operaciju.

Dalje, obično dohvaćamo skup odabranih ključeva za obradu:

Postavi selectedKeys = selector.selectedKeys ();

Skup koji smo dobili je od Tipka za odabir objekata, svaki ključ predstavlja registrirani kanal koji je spreman za operaciju.

Nakon toga obično ponavljamo ovaj skup i za svaki ključ dobivamo kanal i izvodimo bilo koju operaciju koja se pojavljuje u našem interesu postavljenu na njemu.

Tijekom trajanja kanala može se odabrati nekoliko puta jer se njegov ključ pojavljuje u spremnom setu za različite događaje. Zbog toga moramo imati kontinuiranu petlju da bismo uhvatili i obradili kanalske događaje kad i kada se dogode.

8. Potpuni primjer

Kako bismo učvrstili znanje stečeno u prethodnim odjeljcima, izradit ćemo cjelovit primjer klijent-poslužitelj.

Radi lakšeg testiranja našeg koda, izradit ćemo echo poslužitelj i echo klijent. U ovoj vrsti postavljanja, klijent se povezuje s poslužiteljem i počinje mu slati poruke. Poslužitelj ponavlja povratne poruke koje je poslao svaki klijent.

Kada poslužitelj naiđe na određenu poruku, kao što je kraj, tumači ga kao kraj komunikacije i zatvara vezu s klijentom.

8.1. Poslužitelj

Evo našeg koda za EchoServer.java:

javna klasa EchoServer {private static final String POISON_PILL = "POISON_PILL"; public static void main (String [] args) baca IOException {Selector selector = Selector.open (); ServerSocketChannel serverSocket = ServerSocketChannel.open (); serverSocket.bind (nova InetSocketAddress ("localhost", 5454)); serverSocket.configureBlocking (netačno); serverSocket.register (selektor, SelectionKey.OP_ACCEPT); ByteBuffer međuspremnik = ByteBuffer.allocate (256); while (true) {selector.select (); Postavi selectedKeys = selector.selectedKeys (); Iterator iter = selectedKeys.iterator (); while (iter.hasNext ()) {Tipka SelectionKey = iter.next (); if (key.isAcceptable ()) {register (selektor, serverSocket); } if (key.isReadable ()) {answerWithEcho (međuspremnik, ključ); } iter.remove (); }}} privatna statička void answerWithEcho (me uspremnik ByteBuffer, tipka SelectionKey) baca IOException {SocketChannel client = (SocketChannel) key.channel (); client.read (međuspremnik); if (novi String (buffer.array ()). trim (). jednako (POISON_PILL)) {client.close (); System.out.println ("Više ne prihvaća poruke klijenta"); } else {buffer.flip (); client.write (međuspremnik); buffer.clear (); }} privatni statički void registar (selektor selektora, ServerSocketChannel serverSocket) baca IOException {SocketChannel client = serverSocket.accept (); client.configureBlocking (netačno); client.register (selektor, SelectionKey.OP_READ); } javni statički postupak Process () baca IOException, InterruptedException {String javaHome = System.getProperty ("java.home"); Niz javaBin = javaHome + File.separator + "bin" + File.separator + "java"; Niz classpath = System.getProperty ("java.class.path"); Niz klaseName = EchoServer.class.getCanonicalName (); Builder ProcessBuilder = novi ProcessBuilder (javaBin, "-cp", classpath, className); return builder.start (); }}

To se događa; mi stvaramo a Selektor objekt pozivanjem statičkog otvorena metoda. Zatim kanal izrađujemo i pozivanjem njegovog statičkog otvorena metoda, posebno a ServerSocketChannel primjer.

Ovo je zbog ServerSocketChannel je odabirljiv i dobar za strujno orijentiranu utičnicu za slušanje.

Zatim ga vežemo za luku po našem izboru. Sjetimo se da smo ranije rekli da prije registracije selektivnog kanala na selektor, prvo ga moramo postaviti u način koji ne blokira. Dakle, sljedeće radimo ovo, a zatim registriramo kanal na selektor.

Ne trebaju nam Tipka za odabir primjer ovog kanala u ovoj fazi, pa ga se nećemo sjećati.

Java NIO koristi model orijentiran na međuspremnik, a ne stream-orijentirani model. Dakle, komunikacija putem utičnice obično se odvija pisanjem u međuspremnik i čitanjem iz njega.

Mi, dakle, stvaramo novo ByteBuffer na koju će poslužitelj pisati i čitati. Inicijaliziramo ga na 256 bajtova, to je samo proizvoljna vrijednost, ovisno o tome koliko podataka planiramo prenijeti amo-tamo.

Na kraju izvršavamo postupak odabira. Odabiremo spremne kanale, dohvaćamo njihove odabirne tipke, prevlačimo se po tipkama i izvodimo operacije za koje je svaki kanal spreman.

To radimo u beskonačnoj petlji jer poslužitelji obično moraju nastaviti raditi bez obzira postoji li aktivnost ili ne.

Jedina operacija a ServerSocketChannel može podnijeti je PRIHVATITI operacija. Kad prihvatimo vezu od klijenta, dobivamo a SocketChannel objekt na kojem možemo čitati i pisati. Postavili smo ga u neblokirajući način i registrirali za postupak READ na selektoru.

Tijekom jednog od sljedećih odabira, ovaj će novi kanal postati spreman za čitanje. Dohvaćamo ga i očitamo sadržaj u međuspremnik. Istina da je to kao eho poslužitelj, moramo taj sadržaj vratiti klijentu.

Kada želimo pisati u međuspremnik iz kojeg čitamo, moramo nazvati okrenuti () metoda.

Napokon smo postavili međuspremnik u način pisanja pozivom flip metodu i jednostavno joj napišite.

The početak() metoda definirana je tako da se echo poslužitelj može pokrenuti kao zasebni proces tijekom jedinstvenog testiranja.

8.2. Klijent

Evo našeg koda za EchoClient.java:

javna klasa EchoClient {privatni statički klijent SocketChannel; privatni statički međuspremnik ByteBuffer; privatna statička instanca EchoClient; javni statični EchoClient start () {if (instance == null) instance = new EchoClient (); povratna instanca; } javna statička void stop () baca IOException {client.close (); međuspremnik = null; } private EchoClient () {try {client = SocketChannel.open (novi InetSocketAddress ("localhost", 5454)); međuspremnik = ByteBuffer.allocate (256); } catch (IOException e) {e.printStackTrace (); }} javni String sendMessage (String msg) {buffer = ByteBuffer.wrap (msg.getBytes ()); Nizni odgovor = null; probajte {client.write (međuspremnik); buffer.clear (); client.read (međuspremnik); odgovor = novi String (buffer.array ()). trim (); System.out.println ("response =" + odgovor); buffer.clear (); } catch (IOException e) {e.printStackTrace (); } odgovor na povratak; }}

Klijent je jednostavniji od poslužitelja.

Koristimo jednokračni uzorak za instanciranje unutar početak statička metoda. Iz ove metode nazivamo privatni konstruktor.

U privatnom konstruktoru otvaramo vezu na istom portu na koji je povezan kanal poslužitelja i još uvijek na istom hostu.

Zatim stvaramo međuspremnik u koji možemo pisati i iz kojeg čitamo.

Napokon, imamo a Pošalji poruku metoda koja čita obavija bilo koji niz koji mu proslijedimo u bajtni međuspremnik koji se preko kanala prenosi na poslužitelj.

Zatim čitamo s klijentskog kanala kako bismo dobili poruku koju je poslao poslužitelj. Vraćamo ovo kao odjek naše poruke.

8.3. Testiranje

Unutar klase tzv EchoTest.java, stvorit ćemo testni slučaj koji pokreće poslužitelj, šalje poruke poslužitelju i prolazi samo kada iste poruke budu primljene natrag s poslužitelja. Kao posljednji korak, test slučaj zaustavlja poslužitelj prije završetka.

Sada možemo pokrenuti test:

javna klasa EchoTest {Poslužitelj obrade; EchoClient klijent; @Prije javnog void postavljanja () baca IOException, InterruptedException {server = EchoServer.start (); klijent = EchoClient.start (); } @Test javna praznina givenServerClient_whenServerEchosMessage_thenCorrect () {Niz resp1 = client.sendMessage ("zdravo"); Niz resp2 = client.sendMessage ("svijet"); assertEquals ("zdravo", resp1); assertEquals ("svijet", odnosno 2); } @Nakon javne void teardown () baca IOException {server.destroy (); EchoClient.stop (); }}

9. Zaključak

U ovom smo članku pokrili osnovnu upotrebu komponente Java NIO Selector.

Kompletni izvorni kod i svi isječci koda za ovaj članak dostupni su u mom projektu GitHub.


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