Search code examples
javamultithreadingsocketsasynchronousnio2

Java NIO.2 CompletetionHandler not reaching


Java NIO.2 Gurus could anyone explain why this code not working when I'm commenting out the Thread.sleep(...) line.

And what is the elegant way, to tell JVM to proccess asynchronous operations until program finishes?

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class Main {
    public static void main(String[] args) {
        try {
            AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
            channel.connect(new InetSocketAddress("www.yahoo.com", 80),
                    null, new CompletionHandler<Void, Object>() {
                        @Override
                        public void completed(Void result, Object attachment) {
                            System.out.println("Connected");

                            ByteBuffer buffer = ByteBuffer.wrap("GET http://www.yahoo.com HTTP/1.1\r\n\r\n".getBytes());
                            channel.write(buffer, null, new CompletionHandler<Integer, Object>() {
                                @Override
                                public void completed(Integer result, Object attachment) {
                                    System.out.println("Write completed: " + result.toString());

                                    ByteBuffer buffer = ByteBuffer.allocate(23);

                                    channel.read(buffer, null, new CompletionHandler<Integer, Object>() {
                                        @Override
                                        public void completed(Integer result, Object attachment) {
                                            System.out.println("Read completed: " + result.toString());
                                            System.out.println(new String(buffer.array()));
                                        }

                                        @Override
                                        public void failed(Throwable exc, Object attachment) {
                                            exc.printStackTrace();
                                        }
                                    });
                                }

                                @Override
                                public void failed(Throwable exc, Object attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, Object attachment) {
                            exc.printStackTrace();
                        }
                    });

            Thread.sleep(10000);

            channel.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Thanks in advance.


Solution

  • Why it isn't working when Thread.sleep(...) is removed:

    The method connect(SocketAddress remote, A attachment, CompletionHandler<Void,? super A> handler) creates a new Thread in which he connects and invokes the handler.

    So without the delay you are closing the socket before it's connected or all data was transferred.

    How to avoid it

    You have to find a way to wait for the other threads.

    For example with java.util.concurrent.CompletableFuture<V>:

    try {
        CompletableFuture<Boolean> future = new CompletableFuture<Boolean>();
    
        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
        channel.connect(new InetSocketAddress("www.yahoo.com", 80),
                null, new CompletionHandler<Void, Object>() {
                    @Override
                    public void completed(Void result, Object attachment) {
                        System.out.println("Connected");
    
                        ByteBuffer buffer = ByteBuffer.wrap("GET http://www.yahoo.com HTTP/1.1\r\n\r\n".getBytes());
                        channel.write(buffer, null, new CompletionHandler<Integer, Object>() {
                            @Override
                            public void completed(Integer result, Object attachment) {
                                System.out.println("Write completed: " + result.toString());
    
                                ByteBuffer buffer = ByteBuffer.allocate(23);
    
                                channel.read(buffer, null, new CompletionHandler<Integer, Object>() {
                                    @Override
                                    public void completed(Integer result, Object attachment) {
                                        System.out.println("Read completed: " + result.toString());
                                        System.out.println(new String(buffer.array()));
    
                                        // 
                                        future.complete(true);
                                    }
    
                                    @Override
                                    public void failed(Throwable exc, Object attachment) {
                                        exc.printStackTrace();
                                        //
                                        future.complete(false);
                                    }
                                });
                            }
    
                            @Override
                            public void failed(Throwable exc, Object attachment) {
                                exc.printStackTrace();
                                //
                                future.complete(false);
                            }
                        });
                    }
    
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        exc.printStackTrace();
                        //
                        future.complete(false);
                    }
                });
        // Wait until the other Threads are finished
        System.out.println("Successs: "+future.get(10, TimeUnit.SECONDS));
        channel.close();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    

    Or in your case you can simply handle the AsynchronousSocketChannel this way (without the handlers):

        try {
        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
        Future<Void> futureConnect = channel.connect(new InetSocketAddress("www.yahoo.com", 80));
        // wait until connected
        futureConnect.get();
        System.out.println("Connected");
    
        ByteBuffer buffer = ByteBuffer.wrap("GET http://www.yahoo.com HTTP/1.1\r\n\r\n".getBytes());
        Future<Integer> futureWrite = channel.write(buffer);
        // wait until all data is written
        Integer resultWrite = futureWrite.get();
        System.out.println("Write completed: " + resultWrite.toString());
    
        ByteBuffer bufferRead = ByteBuffer.allocate(23);
        Future<Integer> futureRead = channel.read(bufferRead);
        // wait ...
        Integer resultRead = futureRead.get();
        System.out.println("Read completed: " + resultRead.toString());
        System.out.println(new String(bufferRead.array()));
    
        // finnished now the channel can be closed
        channel.close();
    } catch (Exception e) {
        e.printStackTrace();
    }