En guide til NIO2 Asynchronous Socket Channel

1. Oversigt

I denne artikel vil vi demonstrere, hvordan man bygger en simpel server og dens klient ved hjælp af Java 7 NIO.2-kanal-API'er.

Vi ser på AsynchronousServerSocketChannel og AsynchronousSocketChannel klasser, der er de nøgleklasser, der anvendes til implementering af henholdsvis serveren og klienten.

Hvis du er ny med NIO.2-kanal-API'er, har vi en indledende artikel på dette websted. Du kan læse det ved at følge dette link.

Alle klasser, der er nødvendige for at bruge NIO.2-kanal-API'er, er samlet i java.nio.kanaler pakke:

importer java.nio.channels. *;

2. Serveren med Fremtid

En forekomst af AsynchronousServerSocketChannel oprettes ved at kalde den statiske åbne API på sin klasse:

AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open ();

En nyoprettet asynkron serverstikkanal er åben, men endnu ikke bundet, så vi skal binde den til en lokal adresse og eventuelt vælge en port:

server.bind (ny InetSocketAddress ("127.0.0.1", 4555));

Vi kunne lige så godt have passeret nul, så den bruger en lokal adresse og binder sig til en vilkårlig port:

server.bind (null);

Når bundet, den acceptere API bruges til at starte accept af forbindelser til kanalens stikkontakt:

Fremtidig acceptFuture = server.accept ();

Som det er med asynkrone kanaloperationer, vender ovenstående opkald med det samme, og udførelsen fortsætter.

Dernæst kan vi bruge API til forespørgsel efter et svar fra Fremtid objekt:

AsynchronousSocketChannel arbejdstager = future.get ();

Dette opkald blokerer om nødvendigt for at vente på en forbindelsesanmodning fra en klient. Eventuelt kan vi specificere en timeout, hvis vi ikke vil vente for evigt:

AsynchronousSocketChannel-arbejder = acceptFuture.get (10, TimeUnit.SECONDS);

Efter at ovenstående opkald vender tilbage, og operationen var vellykket, kan vi oprette en loop, inden for hvilken vi lytter efter indgående beskeder og ekko dem tilbage til klienten.

Lad os oprette en metode kaldet runServer inden for hvilket vi afventer og behandler indgående beskeder:

offentlig ugyldig runServer () {clientChannel = acceptResult.get (); hvis ((clientChannel! = null) && (clientChannel.isOpen ())) {while (true) {ByteBuffer buffer = ByteBuffer.allocate (32); Fremtidig readResult = clientChannel.read (buffer); // udføre andre beregninger readResult.get (); buffer.flip (); Fremtidig writeResult = clientChannel.write (buffer); // udføre andre beregninger writeResult.get (); buffer.clear (); } clientChannel.close (); serverChannel.close (); }}

Inde i sløjfen er alt, hvad vi gør, at oprette en buffer til at læse fra og skrive til afhængigt af operationen.

Så hver gang vi læser eller skriver, kan vi fortsætte med at udføre enhver anden kode, og når vi er klar til at behandle resultatet, kalder vi få() API på Fremtid objekt.

For at starte serveren kalder vi dens konstruktør og derefter runServer metode indeni vigtigste:

public static void main (String [] args) {AsyncEchoServer server = new AsyncEchoServer (); server.runServer (); }

3. Serveren med AfslutningHandler

I dette afsnit vil vi se, hvordan du implementerer den samme server ved hjælp af AfslutningHandler tilgang snarere end en Fremtid nærme sig.

Inde i konstruktøren opretter vi en AsynchronousServerSocketChannel og binde den til en lokal adresse på samme måde som vi gjorde før:

serverChannel = AsynchronousServerSocketChannel.open (); InetSocketAddress hostAddress = ny InetSocketAddress ("localhost", 4999); serverChannel.bind (hostAddress);

Dernæst opretter vi stadig inde i konstruktøren en while-loop, inden for hvilken vi accepterer enhver indgående forbindelse fra en klient. Dette mens loop bruges strengt til forhindre serveren i at afslutte, før der oprettes forbindelse til en klient.

Til forhindre løkken i at løbe uendeligt, vi ringer System.in.read () i slutningen for at blokere udførelse, indtil en indgående forbindelse læses fra standardindgangsstrømmen:

while (true) {serverChannel.accept (null, new CompletionHandler () {@Override public void completed (AsynchronousSocketChannel result, Object attachment) {if (serverChannel.isOpen ()) {serverChannel.accept (null, this);} clientChannel = resultat; hvis ((clientChannel! = null) && (clientChannel.isOpen ())) {ReadWriteHandler handler = new ReadWriteHandler (); ByteBuffer buffer = ByteBuffer.allocate (32); Map readInfo = new HashMap (); readInfo.put ( "handling", "læs"); readInfo.put ("buffer", buffer); clientChannel.read (buffer, readInfo, handler);}} @ Override public void failed (Throwable exc, Object attachment) {// procesfejl }}); System.in.read (); }

Når der oprettes en forbindelse, afsluttet tilbagekaldsmetode i AfslutningHandler af acceptoperationen kaldes.

Dens returtype er en forekomst af AsynchronousSocketChannel. Hvis serverstikkanalen stadig er åben, kalder vi acceptere API igen for at gøre dig klar til en anden indgående forbindelse, mens du genbruger den samme handler.

Dernæst tildeler vi den returnerede sokkelkanal til en global forekomst. Vi kontrollerer derefter, at det ikke er null, og at det er åbent, før vi udfører operationer på det.

Det punkt, hvor vi kan begynde at læse og skrive, er inden i afsluttet callback API fra acceptere operationens handler. Dette trin erstatter den tidligere tilgang, hvor vi pollede kanalen med API.

Læg mærke til det serveren afslutter ikke længere, efter at der er oprettet en forbindelse medmindre vi eksplicit lukker det.

Bemærk også, at vi oprettede en separat indre klasse til håndtering af læse- og skriveoperationer; ReadWriteHandler. Vi vil se, hvordan vedhæftningsobjektet kommer til nytte på dette tidspunkt.

Lad os først se på ReadWriteHandler klasse:

klasse ReadWriteHandler implementerer CompletionHandler {@ Overstyr offentlig tomrum afsluttet (heltal-resultat, kortvedhæftet fil) {Map actionInfo = vedhæftet fil; String action = (String) actionInfo.get ("action"); if ("read" .equals (action)) {ByteBuffer buffer = (ByteBuffer) actionInfo.get ("buffer"); buffer.flip (); actionInfo.put ("handling", "skriv"); clientChannel.write (buffer, actionInfo, dette); buffer.clear (); } ellers hvis ("skriv" .equals (handling)) {ByteBuffer buffer = ByteBuffer.allocate (32); actionInfo.put ("handling", "læs"); actionInfo.put ("buffer", buffer); clientChannel.read (buffer, actionInfo, dette); }} Offentligt ugyldigt @ overrids mislykkedes (kaste uden, kortvedhæftet fil) {//}}

Den generiske type af vores vedhæftede fil i ReadWriteHandler klasse er et kort. Vi skal specifikt videregive to vigtige parametre igennem det - typen af ​​operation (handling) og bufferen.

Dernæst vil vi se, hvordan disse parametre bruges.

Den første operation, vi udfører, er en Læs da dette er en ekkoserver, der kun reagerer på klientbeskeder. Inde i ReadWriteHandler'S afsluttet tilbagekaldsmetode, vi henter de vedhæftede data og beslutter, hvad vi skal gøre i overensstemmelse hermed.

Hvis det er en Læs operation, der er afsluttet, henter vi bufferen, ændrer vedhæftningens handlingsparameter og udfører en skrive operation med det samme for at ekko beskeden til klienten.

Hvis det er en skrive operation, der lige er afsluttet, kalder vi Læs API igen for at forberede serveren til at modtage en anden indgående besked.

4. Kunden

Efter opsætning af serveren kan vi nu konfigurere klienten ved at ringe til åben API på AsyncronousSocketChannel klasse. Dette opkald opretter en ny forekomst af klientstikkanalen, som vi derefter bruger til at oprette forbindelse til serveren:

AsynchronousSocketChannel-klient = AsynchronousSocketChannel.open (); InetSocketAddress hostAddress = ny InetSocketAddress ("localhost", 4999) Future future = client.connect (hostAddress);

Det Opret forbindelse operation returnerer intet efter succes. Vi kan dog stadig bruge Fremtid mod at overvåge tilstanden for den asynkrone operation.

Lad os kalde API til at afvente forbindelse:

future.get ()

Efter dette trin kan vi begynde at sende meddelelser til serveren og modtage ekkoer for det samme. Det Send besked metoden ser sådan ud:

public String sendMessage (String message) {byte [] byteMsg = new String (message) .getBytes (); ByteBuffer buffer = ByteBuffer.wrap (byteMsg); Fremtidig writeResult = client.write (buffer); // gør noget beregning writeResult.get (); buffer.flip (); Fremtidig readResult = client.read (buffer); // gør noget beregning readResult.get (); Strengeko = ny streng (buffer.array ()). Trim (); buffer.clear (); returnere ekko; }

5. Testen

For at bekræfte, at vores server- og klientapplikationer fungerer efter forventning, kan vi bruge en test:

@Test offentlig ugyldighed givenServerClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hej"); Streng resp2 = client.sendMessage ("verden"); assertEquals ("hej", resp1); assertEquals ("verden", resp2); }

6. Konklusion

I denne artikel har vi udforsket Java NIO.2 asynkrone socketkanal-API'er. Vi har været i stand til at gennemgå processen med at opbygge en server og klient med disse nye API'er.

Du kan få adgang til den fulde kildekode til denne artikel i Github-projektet.


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