Een gids voor NIO2 asynchroon aansluitkanaal

1. Overzicht

In dit artikel laten we zien hoe u een eenvoudige server en zijn client bouwt met behulp van de Java 7 NIO.2-kanaal-API's.

We zullen kijken naar de AsynchronousServerSocketChannel en AsynchronousSocketChannel klassen die de sleutelklassen zijn die worden gebruikt bij het implementeren van respectievelijk de server en de client.

Als u nieuw bent bij NIO.2-kanaal-API's, hebben we een inleidend artikel op deze site. U kunt het lezen door deze link te volgen.

Alle klassen die nodig zijn om NIO.2-kanaal-API's te gebruiken, zijn gebundeld in java.nio.channels pakket:

importeer java.nio.channels. *;

2. De server met Toekomst

Een exemplaar van AsynchronousServerSocketChannel wordt gemaakt door de statische open API aan te roepen in zijn klasse:

AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open ();

Een nieuw aangemaakt asynchroon server-socketkanaal is open maar nog niet gebonden, dus we moeten het binden aan een lokaal adres en optioneel een poort kiezen:

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

We hadden net zo goed null kunnen doorgeven, zodat het een lokaal adres gebruikt en aan een willekeurige poort bindt:

server.bind (null);

Eenmaal gebonden, de aanvaarden API wordt gebruikt om het accepteren van verbindingen met de socket van het kanaal te starten:

Toekomstige acceptFuture = server.accept ();

Net als bij asynchrone kanaalbewerkingen, keert de bovenstaande oproep meteen terug en wordt de uitvoering voortgezet.

Vervolgens kunnen we de krijgen API om een ​​antwoord van het Toekomst voorwerp:

AsynchronousSocketChannel worker = future.get ();

Deze oproep wordt indien nodig geblokkeerd om te wachten op een verbindingsverzoek van een client. Optioneel kunnen we een time-out specificeren als we niet eeuwig willen wachten:

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

Nadat de bovenstaande oproep is teruggekeerd en de bewerking is gelukt, kunnen we een lus maken waarin we naar inkomende berichten luisteren en deze terug naar de client sturen.

Laten we een methode maken met de naam runServer waarbinnen we wachten en eventuele inkomende berichten verwerken:

openbare ongeldige runServer () {clientChannel = acceptResult.get (); if ((clientChannel! = null) && (clientChannel.isOpen ())) {while (true) {ByteBuffer buffer = ByteBuffer.allocate (32); Future readResult = clientChannel.read (buffer); // voer andere berekeningen uit readResult.get (); buffer.flip (); Future writeResult = clientChannel.write (buffer); // voer andere berekeningen uit writeResult.get (); buffer.clear (); } clientChannel.close (); serverChannel.close (); }}

Binnen de lus maken we alleen een buffer om van te lezen en naar te schrijven, afhankelijk van de bewerking.

Elke keer dat we lezen of schrijven, kunnen we doorgaan met het uitvoeren van een andere code en wanneer we klaar zijn om het resultaat te verwerken, bellen we de krijgen() API op het Toekomst voorwerp.

Om de server te starten, noemen we de constructor en vervolgens de runServer methode binnen hoofd:

openbare statische leegte hoofd (String [] args) {AsyncEchoServer server = nieuwe AsyncEchoServer (); server.runServer (); }

3. De server met VoltooiingHandler

In deze sectie zullen we zien hoe u dezelfde server implementeert met behulp van de VoltooiingHandler benadering in plaats van een Toekomst nadering.

Binnen de constructor maken we een AsynchronousServerSocketChannel en bind het op dezelfde manier als voorheen aan een lokaal adres:

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

Vervolgens maken we, nog steeds in de constructor, een while-lus waarin we elke inkomende verbinding van een client accepteren. Deze while-lus wordt strikt gebruikt om voorkomen dat de server afsluit voordat een verbinding met een client tot stand is gebracht.

Naar voorkomen dat de lus eindeloos doorloopt, wij bellen System.in.read () aan het einde om de uitvoering te blokkeren totdat een inkomende verbinding wordt gelezen uit de standaard invoerstroom:

while (true) {serverChannel.accept (null, new CompletionHandler () {@Override public void voltooid (resultaat AsynchronousSocketChannel, objectbijlage) {if (serverChannel.isOpen ()) {serverChannel.accept (null, this);} clientChannel = resultaat; if ((clientChannel! = null) && (clientChannel.isOpen ())) {ReadWriteHandler-handler = nieuwe ReadWriteHandler (); ByteBuffer-buffer = ByteBuffer.allocate (32); Map readInfo = nieuwe HashMap (); readInfo.put ( "action", "read"); readInfo.put ("buffer", buffer); clientChannel.read (buffer, readInfo, handler);}} @Override public void failed (Throwable exc, Object attachment) {// procesfout }}); System.in.read (); }

Als er een verbinding tot stand is gebracht, wordt het voltooid callback-methode in de VoltooiingHandler van de acceptatiebewerking wordt aangeroepen.

Het retourtype is een instantie van AsynchronousSocketChannel. Als het server-socketkanaal nog open is, noemen we het aanvaarden API opnieuw om u voor te bereiden op een nieuwe inkomende verbinding terwijl u dezelfde handler opnieuw gebruikt.

Vervolgens wijzen we het geretourneerde socketkanaal toe aan een globale instantie. We controleren vervolgens of het niet nul is en of het open is voordat we er bewerkingen op uitvoeren.

Het punt waarop we lees- en schrijfbewerkingen kunnen starten, bevindt zich in het voltooid callback-API van de aanvaarden operatie's handler. Deze stap vervangt de vorige benadering waarbij we het kanaal hebben ondervraagd met de krijgen API.

Let erop dat de server zal niet meer afsluiten nadat er een verbinding tot stand is gebracht tenzij we het expliciet sluiten.

Merk ook op dat we een aparte innerlijke klasse hebben gemaakt voor het afhandelen van lees- en schrijfbewerkingen; ReadWriteHandler. We zullen zien hoe het bevestigingsobject op dit punt van pas komt.

Laten we eerst eens kijken naar het ReadWriteHandler klasse:

klasse ReadWriteHandler implementeert CompletionHandler {@Override public void voltooid (resultaat geheel getal, kaartbijlage) {Map actionInfo = bijlage; String action = (String) actionInfo.get ("action"); if ("read" .equals (action)) {ByteBuffer buffer = (ByteBuffer) actionInfo.get ("buffer"); buffer.flip (); actionInfo.put ("action", "write"); clientChannel.write (buffer, actionInfo, dit); buffer.clear (); } else if ("write" .equals (action)) {ByteBuffer buffer = ByteBuffer.allocate (32); actionInfo.put ("action", "read"); actionInfo.put ("buffer", buffer); clientChannel.read (buffer, actionInfo, dit); }} @Override public void is mislukt (Throwable exc, Map attachment) {//}}

Het generieke type van onze bijlage in de ReadWriteHandler klasse is een kaart. We moeten er specifiek twee belangrijke parameters doorheen geven: het type bewerking (actie) en de buffer.

Vervolgens zullen we zien hoe deze parameters worden gebruikt.

De eerste operatie die we uitvoeren is een lezen aangezien dit een echoserver is die alleen op clientberichten reageert. Binnen in de ReadWriteHandler‘S voltooid callback-methode, we halen de bijgevoegde gegevens op en beslissen wat we dienovereenkomstig moeten doen.

Als het een lezen bewerking die is voltooid, halen we de buffer op, wijzigen de actieparameter van de bijlage en voeren een schrijven operatie meteen om het bericht naar de klant te echoën.

Als het een schrijven operatie die zojuist is voltooid, noemen we de lezen API opnieuw om de server voor te bereiden om nog een inkomend bericht te ontvangen.

4. De klant

Nadat we de server hebben ingesteld, kunnen we nu de client instellen door het Open API op het AsyncronousSocketChannel klasse. Deze aanroep maakt een nieuwe instantie van het client-socketkanaal aan die we vervolgens gebruiken om verbinding te maken met de server:

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

De aansluiten operatie levert niets op bij succes. We kunnen echter nog steeds de Toekomst object om de status van de asynchrone bewerking te bewaken.

Laten we de krijgen API om verbinding te maken:

future.get ()

Na deze stap kunnen we beginnen met het verzenden van berichten naar de server en het ontvangen van echo's voor hetzelfde. De bericht versturen methode ziet er als volgt uit:

public String sendMessage (String-bericht) {byte [] byteMsg = nieuwe String (bericht) .getBytes (); ByteBuffer-buffer = ByteBuffer.wrap (byteMsg); Future writeResult = client.write (buffer); // doe wat berekening writeResult.get (); buffer.flip (); Future readResult = client.read (buffer); // voer een berekening uit readResult.get (); String echo = nieuwe String (buffer.array ()). Trim (); buffer.clear (); terugkeer echo; }

5. De test

Om te bevestigen dat onze server- en clienttoepassingen naar verwachting presteren, kunnen we een test gebruiken:

@Test openbare ongeldig gegevenServerClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hallo"); String resp2 = client.sendMessage ("wereld"); assertEquals ("hallo", resp1); assertEquals ("wereld", resp2); }

6. Conclusie

In dit artikel hebben we de Java NIO.2 asynchrone socketkanaal-API's onderzocht. Met deze nieuwe API's hebben we het proces van het bouwen van een server en client kunnen doorlopen.

U kunt de volledige broncode voor dit artikel openen in het Github-project.