Bevezetés a Java NIO Selector-ba

1. Áttekintés

Ebben a cikkben a Java NIO bevezető részeit tárjuk fel Választó összetevő.

A választó mechanizmust biztosít egy vagy több NIO csatorna megfigyelésére és felismerésére, amikor egy vagy több elérhetővé válik adatátvitel céljából.

Ily módon, egyetlen szál használható több csatorna kezelésére, és ezáltal több hálózati kapcsolat.

2. Miért érdemes választót használni?

A választóval több szál helyett több szálat használhatunk több csatorna kezelésére. A szálak közötti konvertálás drága az operációs rendszer számára, és ezen felül minden szál memóriát foglal el.

Ezért minél kevesebb szálat használunk, annál jobb. Fontos azonban erre emlékezni a modern operációs rendszerek és a CPU-k folyamatosan javítják a multitasking feladatokat, így a többszálas menetek idővel folyamatosan csökkennek.

Itt fogunk foglalkozni azzal, hogy miként kezelhetünk több csatornát egyetlen szálon egy választó segítségével.

Vegye figyelembe azt is, hogy a választók nem csak az adatok olvasásában segítenek; hallgathatják a bejövő hálózati kapcsolatokat és lassú csatornákon keresztül írhatnak adatokat.

3. Beállítás

A választó használatához nincs szükség külön beállításra. Minden osztály, amire szükségünk van, a mag java.nio csomagot, és csak importálnunk kell, amire szükségünk van.

Ezt követően több csatornát regisztrálhatunk egy választóobjektummal. Amikor bármely csatornán I / O tevékenység történik, a választó értesít minket. Így olvashatunk nagyszámú adatforrásból egyetlen szálból.

Bármely csatornának, amelyet egy szelektorral regisztrálunk, annak alosztályának kell lennie VálaszthatóChannel. Ezek egy speciális típusú csatornák, amelyek nem blokkoló üzemmódba helyezhetők.

4. Válogató létrehozása

A statikus hivatkozással létrehozható egy választó nyisd ki módszere Választó osztály, amely a rendszer alapértelmezett választó szolgáltatóját használja egy új választó létrehozására:

Selector selector = Selector.open ();

5. Választható csatornák regisztrálása

Annak érdekében, hogy a választó bármely csatornát figyelemmel kísérhesse, regisztrálnunk kell ezeket a csatornákat a választón. Ezt a Regisztráció a választható csatorna módszere.

Mielőtt azonban egy csatornát regisztrálnának egy választónál, nem blokkoló módban kell lennie:

channel.configureBlocking (hamis); SelectionKey kulcs = channel.register (választó, SelectionKey.OP_READ);

Ez azt jelenti, hogy nem használhatjuk FileChannels egy választóval, mivel nem kapcsolhatók nem blokkoló üzemmódba, ahogyan a foglalatcsatornákkal.

Az első paraméter a Választó korábban létrehozott objektum, a második paraméter meghatároz egy érdeklődési kört, azt jelenti, hogy milyen eseményeket kívánunk hallgatni a megfigyelt csatornán, a választón keresztül.

Négy különböző esemény van, amelyeket meghallgathatunk, mindegyiket egy konstans képviseli a SelectionKey osztály:

  • Csatlakozás amikor az ügyfél megpróbál csatlakozni a szerverhez. Képviselője SelectionKey.OP_CONNECT
  • Elfogad amikor a szerver elfogad egy kapcsolatot az ügyféltől. Képviselője SelectionKey.OP_ACCEPT
  • Olvas amikor a szerver készen áll a csatornáról történő olvasásra. Képviselője SelectionKey.OP_READ
  • Ír amikor a szerver készen áll a csatornára írni. Képviselője SelectionKey.OP_WRITE

A visszaküldött tárgy SelectionKey képviseli a választható csatorna regisztrációját a választóval. A következő szakaszban tovább vizsgáljuk.

6. A SelectionKey Tárgy

Amint az előző szakaszban láttuk, amikor egy csatornát regisztrálunk egy választóval, akkor a SelectionKey tárgy. Ez az objektum a csatorna regisztrációját reprezentáló adatokat tárol.

Néhány fontos tulajdonságot tartalmaz, amelyeket jól meg kell értenünk ahhoz, hogy használni tudjuk a választót a csatornán. Ezeket a tulajdonságokat a következő alfejezetekben vizsgáljuk meg.

6.1. Az érdeklődési kör

Az érdeklődési kör meghatározza azon események halmazát, amelyekre azt szeretnénk, hogy a választó figyeljen ezen a csatornán. Ez egy egész érték; ezeket az információkat a következő módon kaphatjuk meg.

Először is megkapjuk a SelectionKey’S interestOps módszer. Aztán az esemény állandó SelectionKey korábban megnéztük.

Amikor mi ÉS ez a két érték, kapunk egy logikai értéket, amely megmondja, hogy figyeljük-e az eseményt vagy sem:

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

6.2. A Kész készlet

A kész készlet meghatározza azoknak az eseményeknek a halmazát, amelyekre a csatorna készen áll. Ez egy egész szám is; ezeket az információkat a következő módon kaphatjuk meg.

A kész készletet visszaadtuk SelectionKey’S readyOps módszer. Amikor mi ÉS ez az érték az eseményállandókkal állandó, mint az érdeklődési halmaz esetében, akkor kapunk egy logikai értéket, amely azt mutatja, hogy a csatorna készen áll-e egy adott értékre.

Ennek másik alternatívája és rövidebb módja a használat SelectionKey 'kényelmi módszerei ugyanarra a célra:

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

6.3. A csatorna

A nézett csatornához való hozzáférés a SelectionKey objektum nagyon egyszerű. Csak hívjuk a csatorna módszer:

Csatorna csatorna = kulcs.csatorna ();

6.4. A választó

Csakúgy, mint egy csatorna megszerzése, nagyon könnyű megszerezni a csatornát Választó objektum a SelectionKey tárgy:

Selector selector = kulcs.selector ();

6.5. Objektumok csatolása

Tárgyat csatolhatunk a SelectionKey. Előfordulhat, hogy meg akarunk adni egy csatornának egyedi azonosítót, vagy bármilyen Java objektumot csatolunk, amelyet nyomon követhetünk.

A tárgyak rögzítése praktikus módszer erre. Így csatolhat objektumokat és szerezhet be a SelectionKey:

kulcs.attach (Object); Object object = kulcs.attachment ();

Alternatív megoldásként választhatunk egy objektum csatolását a csatorna regisztrációja során. Hozzáadjuk harmadik paraméterként a csatorna paramétereihez Regisztráció módszer, így:

SelectionKey kulcs = channel.register (választó, SelectionKey.OP_ACCEPT, objektum);

7. Csatorna kulcs kiválasztása

Eddig megvizsgáltuk, hogyan hozhatunk létre egy választót, regisztrálhatunk rá csatornákat és ellenőrizhetjük a SelectionKey objektum, amely egy csatorna regisztrációját jelöli a választó számára.

Ez csak a folyamat fele, most egy folyamatos folyamatot kell végrehajtanunk a kész készlet kiválasztására, amelyet korábban megvizsgáltunk. A kiválasztást a választókkal végezzük válassza módszer, így:

int csatornák = szelektor.select ();

Ez a módszer addig blokkol, amíg legalább egy csatorna készen áll a műveletre. A visszatérő egész szám azt a kulcsot jelenti, amelynek csatornái készen állnak a műveletre.

Ezután általában lekérjük a kiválasztott kulcsok készletét feldolgozásra:

Set selectedKeys = selector.selectedKeys () beállítása;

Az a készlet, amelyből megszereztük SelectionKey objektumok, minden kulcs egy regisztrált csatornát képvisel, amely készen áll a műveletre.

Ezek után általában iterálunk ezen a halmazon, és minden egyes kulcshoz megszerezzük a csatornát, és elvégezzük a rajta meghatározott érdeklődésünknek megfelelő bármely műveletet.

A csatorna élettartama alatt többször is kiválasztható, mivel a kulcsa megjelenik a különféle események kész készleteiben. Ezért van szükségünk folyamatos hurokra, hogy rögzítsük és feldolgozzuk a csatornaeseményeket, amikor és amikor bekövetkeznek.

8. Teljes példa

Az előző szakaszokban megszerzett ismeretek megerősítése érdekében egy teljes kliens-szerver példát fogunk készíteni.

A kód tesztelésének megkönnyítése érdekében felépítünk egy echo szervert és egy echo klienst. Ilyen beállítás esetén az ügyfél csatlakozik a szerverhez, és üzeneteket kezd neki küldeni. A szerver visszhangozza az egyes kliensek által küldött üzeneteket.

Amikor a szerver egy adott üzenettel találkozik, például vége, a kommunikáció végeként értelmezi, és lezárja a kapcsolatot az ügyféllel.

8.1. A szerver

Itt van a kódunk EchoServer.java:

public class EchoServer {private static final String POISON_PILL = "POISON_PILL"; public static void main (String [] args) dobja az IOException {Selector selector = Selector.open (); ServerSocketChannel serverSocket = ServerSocketChannel.open (); serverSocket.bind (új InetSocketAddress ("localhost", 5454)); serverSocket.configureBlocking (hamis); serverSocket.register (választó, SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate (256); while (igaz) {selector.select (); Set selectedKeys = selector.selectedKeys () beállítása; Iterátor iter = selectedKeys.iterator (); while (iter.hasNext ()) {SelectionKey kulcs = iter.next (); if (key.isAcceptable ()) {register (választó, serverSocket); } if (key.isReadable ()) {answerWithEcho (puffer, kulcs); } iter.remove (); }}} private static void answerWithEcho (ByteBuffer puffer, SelectionKey kulcs) dobja az IOException {SocketChannel kliens = (SocketChannel) kulcsot.csatorna (); client.read (puffer); if (új String (puffer.array ()). trim (). egyenlő (POISON_PILL)) {client.close (); System.out.println ("Már nem fogadja el az ügyfélüzeneteket"); } else {buffer.flip (); client.write (puffer); puffer.clear (); }} private static void register (Selector selector, ServerSocketChannel serverSocket) dobja az IOException {SocketChannel kliens = serverSocket.accept (); client.configureBlocking (hamis); client.register (választó, SelectionKey.OP_READ); } public static Process start () dobja az IOException, InterruptedException {String javaHome = System.getProperty ("java.home"); Karakterlánc javaBin = javaHome + File.separator + "bin" + File.separator + "java"; String classpath = System.getProperty ("java.class.path"); String className = EchoServer.class.getCanonicalName (); ProcessBuilder builder = new ProcessBuilder (javaBin, "-cp", classpath, className); return builder.start (); }}

Ez történik; létrehozunk egy Választó objektumot a statikus meghívásával nyisd ki módszer. Ezután létrehozunk egy csatornát úgy is, hogy statikusnak hívjuk nyisd ki módszer, konkrétan a ServerSocketChannel példa.

Ez azért van, mert ServerSocketChannel választható és jó a stream-orientált hallgatási aljzathoz.

Ezután az általunk választott kikötőhöz kötjük. Ne felejtsük el, hogy korábban azt mondtuk, hogy mielőtt egy választható csatornát regisztrálhatnánk egy választóba, először nem blokkoló üzemmódra kell állítanunk. Tehát ezután ezt tesszük, majd regisztráljuk a csatornát a választóba.

Nincs szükségünk a SelectionKey ennek a csatornának a példánya ebben a szakaszban, ezért nem fogunk emlékezni rá.

A Java NIO pufferorientált modellt használ, amely nem folyamorientált modell. Tehát a socket kommunikáció általában úgy történik, hogy egy pufferbe írunk és leolvasunk róla.

Ezért létrehozunk egy újat ByteBuffer amelyet a szerver ír és olvas. Inicializáljuk 256 bájtra, ez csak egy tetszőleges érték, attól függően, hogy mennyi adatot tervezünk oda-vissza átvinni.

Végül elvégezzük a kiválasztási folyamatot. Kiválasztjuk a kész csatornákat, lekérjük azok választógombjait, iterálunk a billentyűk felett, és végrehajtjuk azokat a műveleteket, amelyekre az egyes csatornák készen állnak.

Ezt végtelen ciklusban tesszük, mivel a szervereknek általában folytatni kell a futást, függetlenül attól, hogy van-e tevékenység vagy sem.

Az egyetlen művelet a ServerSocketChannel tudja kezelni egy ELFOGAD művelet. Amikor elfogadjuk a kapcsolatot egy klienstől, megszerezzük a SocketChannel objektum, amelyen olvashatunk és írhatunk. Nem blokkoló üzemmódra állítjuk, és READ műveletként regisztráljuk a választóba.

A következő kiválasztások egyikében ez az új csatorna készen áll az olvasásra. Megkérjük és tartalmát beolvassuk a pufferbe. Visszhangzó szerverként hűen vissza kell írnunk ezt a tartalmat az ügyfélnek.

Amikor egy olyan pufferre szeretnénk írni, amelyről olvastunk, akkor meg kell hívnunk a flip () módszer.

Végül a puffert írási módra állítottuk a flip módszerrel, és egyszerűen írjon hozzá.

A Rajt() A metódust úgy definiáljuk, hogy az echo szerver külön folyamatként elindítható legyen az egység tesztelése során.

8.2. Az ügyfél

Itt van a kódunk EchoClient.java:

public class EchoClient {privát statikus SocketChannel kliens; privát statikus ByteBuffer puffer; privát statikus EchoClient példány; public static EchoClient start () {if (instance == null) instance = new EchoClient (); visszatérési példány; } public static void stop () dobja az IOException {client.close (); puffer = null; } privát EchoClient () {try {client = SocketChannel.open (új InetSocketAddress ("localhost", 5454)); puffer = ByteBuffer.allocate (256); } catch (IOException e) {e.printStackTrace (); }} public String sendMessage (String msg) {puffer = ByteBuffer.wrap (msg.getBytes ()); Karakterlánc-válasz = null; próbáld ki az {client.write (buffer) parancsot; puffer.clear (); client.read (puffer); válasz = új karakterlánc (puffer.array ()). trim (); System.out.println ("válasz =" + válasz); puffer.clear (); } catch (IOException e) {e.printStackTrace (); } visszatérési válasz; }}

Az ügyfél egyszerűbb, mint a szerver.

Egy szingulett mintát használunk arra, hogy azt a Rajt statikus módszer. Ebből a módszerből hívjuk a magánépítőt.

A privát konstruktorban ugyanabban a porton nyitunk kapcsolatot, amelyre a szervercsatorna kötött, és továbbra is ugyanazon a gazdagépen van.

Ezután létrehozunk egy puffert, amelyre írhatunk, és amelyből kiolvashatunk.

Végül van egy üzenet küldése A leolvasó módszer az átadott esetleges karakterláncokat egy bájtpufferbe tekeri, amelyet a csatornán továbbítanak a szerverre.

Ezután olvasunk az ügyfélcsatornáról, hogy megkapjuk a szerver által küldött üzenetet. Ezt üzenetünk visszhangjaként visszaküldjük.

8.3. Tesztelés

Belül egy osztály nevű EchoTest.java, létrehozunk egy tesztesetet, amely elindítja a szervert, üzeneteket küld a szervernek és csak akkor megy át, ha ugyanazokat az üzeneteket visszakapják a szerverről. Utolsó lépésként a teszteset a befejezés előtt leállítja a szervert.

Most lefuttathatjuk a tesztet:

public class EchoTest {Process server; EchoClient kliens; @A nyilvános void beállítása előtt () dobja az IOException, InterruptedException {server = EchoServer.start (); kliens = EchoClient.start (); } @Test public void givenServerClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hello"); Karakterlánc resp2 = client.sendMessage ("világ"); assertEquals ("hello", resp1); assertEquals ("világ", ill2); } @A nyilvános void lebontása () után az IOException dob {server.destroy (); EchoClient.stop (); }}

9. Következtetés

Ebben a cikkben kitértünk a Java NIO Selector összetevő alapvető használatára.

A cikk teljes forráskódja és összes kódrészlete elérhető a GitHub projektemben.