Úvod do výberu Java NIO

1. Prehľad

V tomto článku sa budeme venovať úvodným častiam Java NIO Selektor zložka.

Selektor poskytuje mechanizmus na monitorovanie jedného alebo viacerých kanálov NIO a rozpoznávanie, keď je jeden alebo viac dostupných pre prenos údajov.

Tadiaľto, jedno vlákno je možné použiť na správu viacerých kanálov, a teda viac sieťových pripojení.

2. Prečo používať selektor?

Pomocou selektora môžeme na správu viacerých kanálov použiť jedno vlákno namiesto niekoľkých. Kontextové prepínanie medzi vláknami je pre operačný systém drahéa navyše každé vlákno zaberá pamäť.

Čím menej vlákien použijeme, tým lepšie. Je však dôležité pamätať na to moderné operačné systémy a CPU sa zlepšujú v multitaskingu, takže réžia viacerých závitov sa časom časom zmenšuje.

Budeme sa tu zaoberať tým, ako môžeme pomocou selektora zvládnuť viac kanálov s jedným vláknom.

Pamätajte tiež, že selektory vám nielen pomáhajú čítať údaje; môžu tiež počúvať prichádzajúce sieťové pripojenia a zapisovať údaje cez pomalé kanály.

3. Inštalácia

Na použitie selektora nepotrebujeme žiadne špeciálne nastavenie. Všetky triedy, ktoré potrebujeme, sú jadrom java.nio balíček a my musíme len doviezť to, čo potrebujeme.

Potom môžeme pomocou selektorového objektu zaregistrovať viac kanálov. Keď sa stane I / O aktivita na ktoromkoľvek z kanálov, selektor nás na to upozorní. Takto môžeme čítať z veľkého množstva zdrojov údajov z jedného vlákna.

Akýkoľvek kanál, ktorý zaregistrujeme pomocou selektora, musí byť podtriedou SelectableChannel. Jedná sa o špeciálny typ kanálov, ktoré je možné uviesť do neblokujúceho režimu.

4. Vytvorenie selektora

Selektor je možné vytvoriť vyvolaním statickej kópie otvorené metóda Selektor triedy, ktorá použije predvoleného poskytovateľa selektorov systému na vytvorenie nového selektora:

Selektor selektor = Selector.open ();

5. Registrácia voliteľných kanálov

Aby selektor mohol sledovať akékoľvek kanály, musíme tieto kanály zaregistrovať pomocou selektora. Robíme to vyvolaním Registrovať metóda voliteľného kanálu.

Ale predtým, ako je kanál zaregistrovaný pomocou selektora, musí byť v neblokujúcom režime:

channel.configureBlocking (false); Kľúč SelectionKey = channel.register (selektor, SelectionKey.OP_READ);

To znamená, že nemôžeme použiť FileChannels voličom, pretože ich nemožno prepnúť do neblokujúceho režimu tak, ako to robíme so zásuvkovými kanálmi.

Prvým parametrom je Selektor objekt, ktorý sme vytvorili skôr, druhý parameter definuje množinu úrokov, To znamená, aké udalosti nás zaujímajú na sledovanom kanáli prostredníctvom selektora.

Existujú štyri rôzne udalosti, ktoré môžeme počúvať, každú z nich predstavuje konštanta v SelectionKey trieda:

  • Pripojte sa keď sa klient pokúsi pripojiť k serveru. Reprezentovaný SelectionKey.OP_CONNECT
  • súhlasiť keď server akceptuje pripojenie od klienta. Reprezentovaný SelectionKey.OP_ACCEPT
  • Čítať keď je server pripravený na čítanie z kanála. Reprezentovaný SelectionKey.OP_READ
  • Napíš keď je server pripravený na zápis na kanál. Reprezentovaný SelectionKey.OP_WRITE

Vrátený objekt SelectionKey predstavuje registráciu voliteľného kanála pomocou selektora. Pozrime sa na to ďalej v nasledujúcej časti.

6. SelectionKey Objekt

Ako sme videli v predchádzajúcej časti, keď zaregistrujeme kanál pomocou selektora, dostaneme a SelectionKey objekt. Tento objekt obsahuje údaje predstavujúce registráciu kanála.

Obsahuje niektoré dôležité vlastnosti, ktorým musíme dobre rozumieť, aby sme mohli používať selektor na kanáli. Na tieto vlastnosti sa pozrieme v nasledujúcich pododdieloch.

6.1. Sada úrokov

Sada záujmov definuje množinu udalostí, na ktoré chceme, aby si selektor na tomto kanáli dával pozor. Je to celočíselná hodnota; tieto informácie môžeme získať nasledujúcim spôsobom.

Najskôr máme úrok vrátený spoločnosťou SelectionKey‘S interestOps metóda. Potom máme konštantu udalosti v SelectionKey pozreli sme sa skôr.

Keď A A tieto dve hodnoty dostaneme boolovskú hodnotu, ktorá nám hovorí, či sa na udalosť sleduje alebo nie:

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. Ready Set

Pripravená sada definuje množinu udalostí, na ktoré je kanál pripravený. Je to tiež celočíselná hodnota; tieto informácie môžeme získať nasledujúcim spôsobom.

Pripravenú súpravu sme vrátili SelectionKey‘S readyOps metóda. Keď A A táto hodnota s konštantami udalostí, ako sme to urobili v prípade záujmovej množiny, dostaneme boolean predstavujúci, či je kanál pripravený na konkrétnu hodnotu alebo nie.

Ďalším alternatívnym a kratším spôsobom, ako to dosiahnuť, je použitie SelectionKey 's pohodlnými metódami na ten istý účel:

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

6.3. Kanál

Prístup k sledovanému kanálu z SelectionKey objekt je veľmi jednoduchý. Len voláme kanál metóda:

Kanál kanál = key.channel ();

6.4. Selektor

Rovnako ako získanie kanálu, je veľmi ľahké získať Selektor objekt z SelectionKey objekt:

Selektor selektor = key.selector ();

6.5. Pripevnenie predmetov

Môžeme pripojiť predmet k a SelectionKey. Niekedy možno budeme chcieť dať kanálu vlastné ID alebo pripojiť akýkoľvek druh objektu Java, o ktorom by sme chceli mať prehľad.

Pripevňovanie predmetov je praktický spôsob, ako to urobiť. Takto pripájate a získavate objekty z a SelectionKey:

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

Prípadne sa môžeme rozhodnúť pripojiť objekt počas registrácie kanála. Pridali sme ho ako tretí parameter do kanála Registrovať metóda, napríklad takto:

Kľúč SelectionKey = channel.register (selektor, SelectionKey.OP_ACCEPT, objekt);

7. Výber kľúča kanála

Doteraz sme sa pozreli na to, ako vytvoriť selektor, zaregistrovať k nemu kanály a skontrolovať vlastnosti SelectionKey objekt, ktorý predstavuje registráciu kanála k selektoru.

Toto je iba polovica procesu, teraz musíme vykonať nepretržitý proces výberu pripravenej sady, na ktorú sme sa pozreli skôr. Výber robíme pomocou selektora vyberte metóda, napríklad takto:

int kanály = selector.select ();

Táto metóda blokuje, kým aspoň jeden kanál nie je pripravený na operáciu. Vrátené celé číslo predstavuje počet kľúčov, ktorých kanály sú pripravené na operáciu.

Ďalej zvyčajne načítame množinu vybraných kľúčov na spracovanie:

Nastaviť selectedKeys = selector.selectedKeys ();

Sada, ktorú sme získali, je z SelectionKey objekty, každý kľúč predstavuje registrovaný kanál, ktorý je pripravený na operáciu.

Potom zvyčajne iterujeme túto množinu a pre každý kľúč získame kanál a vykonáme akékoľvek operácie, ktoré sa na ňom objavia v našom záujme.

Počas životnosti kanála ho možno zvoliť niekoľkokrát, pretože sa jeho kľúč objaví v pripravenej množine pre rôzne udalosti. To je dôvod, prečo musíme mať nepretržitú slučku na zachytenie a spracovanie udalostí kanála, akonáhle k nim dôjde.

8. Kompletný príklad

Aby sme upevnili vedomosti, ktoré sme získali v predchádzajúcich častiach, vytvoríme kompletný príklad klient-server.

Pre uľahčenie testovania nášho kódu zostavíme echo server a echo klienta. Pri tomto druhu nastavenia sa klient pripojí k serveru a začne naň odosielať správy. Server odráža spätné správy odoslané každým klientom.

Keď sa server stretne s konkrétnou správou, ako napr koniec, interpretuje to ako koniec komunikácie a ukončí spojenie s klientom.

8.1. Server

Tu je náš kód pre EchoServer.java:

verejná trieda EchoServer {private static final String POISON_PILL = "POISON_PILL"; public static void main (String [] args) hodí IOException {Selector selector = Selector.open (); ServerSocketChannel serverSocket = ServerSocketChannel.open (); serverSocket.bind (nová InetSocketAddress ("localhost", 5454)); serverSocket.configureBlocking (false); serverSocket.register (selektor, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate (256); while (true) {selector.select (); Nastaviť selectedKeys = selector.selectedKeys (); Iterátor iter = selectedKeys.iterator (); while (iter.hasNext ()) {kľúč SelectionKey = iter.next (); if (key.isAcceptable ()) {register (selector, serverSocket); } if (key.isReadable ()) {answerWithEcho (buffer, key); } iter.remove (); }}} private static void answerWithEcho (ByteBuffer buffer, klíč SelectionKey) hodí IOException {SocketChannel client = (SocketChannel) key.channel (); client.read (buffer); if (new String (buffer.array ()). trim (). equals (POISON_PILL)) {client.close (); System.out.println ("Už neprijímam správy klientov"); } else {buffer.flip (); client.write (buffer); buffer.clear (); }} private static void register (selector selector, ServerSocketChannel serverSocket) hodí IOException {SocketChannel client = serverSocket.accept (); client.configureBlocking (false); client.register (selector, SelectionKey.OP_READ); } public static Process start () throws IOException, InterruptedException {String javaHome = System.getProperty ("java.home"); Reťazec javaBin = javaHome + File.separator + "bin" + File.separator + "java"; Reťazec classpath = System.getProperty ("java.class.path"); Reťazec className = EchoServer.class.getCanonicalName (); Builder ProcessBuilder = nový ProcessBuilder (javaBin, "-cp", classpath, className); návrat builder.start (); }}

Toto sa deje; tvoríme a Selektor objekt volaním statického otvorené metóda. Potom vytvoríme kanál aj tak, že zavoláme jeho statický otvorené metóda, konkrétne a ServerSocketChannel inštancia.

To je preto, že ServerSocketChannel je voliteľný a vhodný pre prúdovo orientovanú zásuvku na počúvanie.

Potom ho pripojíme k portu podľa nášho výberu. Pamätajte, že sme už povedali, že pred registráciou voliteľného kanálu do selektora ho musíme najskôr nastaviť do neblokujúceho režimu. Ďalej to urobíme a potom zaregistrujeme kanál do selektora.

Nepotrebujeme SelectionKey inštancie tohto kanála v tejto fáze, takže si to nebudeme pamätať.

Java NIO používa iný model orientovaný na medzipamäť ako model orientovaný na prúd. Komunikácia soketu teda zvyčajne prebieha zápisom do a čítaním z medzipamäte.

Preto tvoríme nový ByteBuffer do ktorého bude server zapisovať a čítať z neho. Inicializujeme to na 256 bajtov, je to len ľubovoľná hodnota, v závislosti od toho, koľko dát plánujeme preniesť sem a tam.

Nakoniec vykonáme výberový proces. Vyberieme pripravené kanály, načítame ich výberové kľúče, prechádzame cez tieto kľúče a vykonávame operácie, pre ktoré je každý kanál pripravený.

Robíme to v nekonečnej slučke, pretože servery musia zvyčajne neustále bežať, či už je alebo nie je v činnosti.

Jedinou operáciou a ServerSocketChannel zvládne je SÚHLASIŤ prevádzka. Keď prijmeme spojenie od klienta, získame a SocketChannel objekt, na ktorom môžeme robiť čítanie a zápis. Nastavíme ho do neblokujúceho režimu a zaregistrujeme ho na operáciu READ do selektora.

Počas jedného z nasledujúcich výberov bude tento nový kanál pripravený na čítanie. Získame ju a načítame jej obsah do medzipamäte. Pravda, je to ako server ozveny, musíme tento obsah zapísať späť klientovi.

Ak chceme zapisovať do medzipamäte, z ktorej sme čítali, musíme zavolať znak flip () metóda.

Nakoniec sme nastavili vyrovnávaciu pamäť na režim zápisu zavolaním prevrátiť metóda a jednoducho do nej napíš.

The štart () metóda je definovaná tak, aby bolo možné echo server spustiť ako samostatný proces počas testovania jednotky.

8.2. Klient

Tu je náš kód pre EchoClient.java:

verejná trieda EchoClient {súkromný statický klient SocketChannel; súkromná statická vyrovnávacia pamäť ByteBuffer; súkromná statická inštancia EchoClient; public static EchoClient start () {if (instance == null) instance = new EchoClient (); návratová inštancia; } public static void stop () hodí IOException {client.close (); buffer = null; } private EchoClient () {try {client = SocketChannel.open (new InetSocketAddress ("localhost", 5454)); buffer = ByteBuffer.allocate (256); } catch (IOException e) {e.printStackTrace (); }} public String sendMessage (String msg) {buffer = ByteBuffer.wrap (msg.getBytes ()); Reťazcová odpoveď = null; skus {client.write (buffer); buffer.clear (); client.read (buffer); response = new String (buffer.array ()). trim (); System.out.println ("response =" + odpoveď); buffer.clear (); } catch (IOException e) {e.printStackTrace (); } návratová odpoveď; }}

Klient je jednoduchší ako server.

Na vytvorenie inštancie vo vnútri súboru používame singletonový vzor začať statická metóda. Z tejto metódy voláme súkromného konštruktora.

V súkromnom konštruktore otvoríme pripojenie na rovnakom porte, na ktorom bol viazaný kanál servera, a stále na rovnakom hostiteľovi.

Potom vytvoríme medzipamäť, do ktorej môžeme písať a z ktorej môžeme čítať.

Nakoniec tu máme a poslať správu metóda, ktorá číta, zalomí akýkoľvek reťazec, ktorý mu odovzdáme, do bajtového bufferu, ktorý sa prenáša cez kanál na server.

Potom čítame z klientskeho kanála, aby sme dostali správu odoslanú serverom. Toto vraciame ako ozvenu našej správy.

8.3. Testovanie

Vo vnútri triedy sa volá EchoTest.java, Chystáme sa vytvoriť testovací prípad, ktorý spustí server, odošle správy na server a prejde iba vtedy, keď sa tie isté správy prijmú späť zo servera. Posledným krokom je, že testovací prípad zastaví server pred dokončením.

Teraz môžeme spustiť test:

verejná trieda EchoTest {Process server; Klient EchoClient; @ Pred public void setup () vyvolá IOException, InterruptedException {server = EchoServer.start (); klient = EchoClient.start (); } @Test public void givenServerClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("ahoj"); Reťazec resp2 = client.sendMessage ("svet"); assertEquals ("ahoj", resp1); assertEquals ("svet", resp2); } @After public void teardown () vyvolá IOException {server.destroy (); EchoClient.stop (); }}

9. Záver

V tomto článku sme sa venovali základnému použitiu komponentu Java NIO Selector.

Kompletný zdrojový kód a všetky útržky kódu pre tento článok sú k dispozícii v mojom projekte GitHub.


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