AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时得到通知,接着就由这个线程自行进行IO操作,IO操作本身还是同步的。NIO 2.0引入了新的异步通道的概念, 并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取获取操作结果:

  • 通过 java.util.concurrent.Future类来表示异步操作的结果
  • 在执行异步操作的时候传入一个java.nio.channels.CompletionHandler接口的实现类作为操作完成的回调 。

NIO2.0的异步套接字通道是真正的异步非阻塞I/O, 对应于UNIX网络编程中的事件驱动 I/O ( AIO)。它不需要通过多路复用器 ( Selector) 对注册的通道进行轮询操作即可实现异步读写 , 从而简化NIO的编程模型 。

在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。

在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。

  • void completed(V result, A attachment);
  • void failed(Throwable exc, A attachment);

说了这么多,感觉很像Ajax中的回调方法,只不过换成了类。直接看网友提供的代码吧:

服务端:

public class AIOEchoServer implements Runnable{
    public final static int PORT = 8001;
    public final static String IP = "127.0.0.1";


    private AsynchronousServerSocketChannel server = null;

    public AIOEchoServer() {
        try {
            ExecutorService executor = Executors.newFixedThreadPool(20);
            AsynchronousChannelGroup asyncChannelGroup = AsynchronousChannelGroup.withThreadPool(executor);
            server = AsynchronousServerSocketChannel.open(asyncChannelGroup).bind(new InetSocketAddress(IP, PORT));
            //同样是利用工厂方法产生一个通道,异步通道 AsynchronousServerSocketChannel
//            server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(IP,PORT));
            //通过setOption配置Socket
            server.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            server.setOption(StandardSocketOptions.SO_RCVBUF, 16 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //使用这个通道(server)来进行客户端的接收和处理
    @Override
    public void run() {
        System.out.println("Server listen on " + PORT);

        //注册事件和事件完成后的处理器,这个CompletionHandler就是事件完成后的处理器
        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

            final ByteBuffer buffer = ByteBuffer.allocate(1024);

            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {

                System.out.println(Thread.currentThread().getName());
                Future<Integer> writeResult = null;

                try {
                    buffer.clear();
                    //把socket中的数据读取到buffer中
                    result.read(buffer).get(100, TimeUnit.SECONDS);
                    System.out.println("In server: " + new String(buffer.array()));
                    buffer.flip();
                    //将数据写回客户端
                    writeResult = result.write(buffer);
                    buffer.flip();
                } catch (InterruptedException | ExecutionException | TimeoutException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        System.out.println(writeResult.get());
                        //关闭处理完的socket,并重新调用accept等待新的连接
                        result.close();
                        server.accept(null, this);
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("failed:" + exc);
            }

        });
    }
  public static void main(String[] args) {
        //因为AIO不会阻塞调用进程,因此必须在主进程阻塞,才能保持进程存活。
        new Thread(new AIOEchoServer()).start();
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

客户端:

public class AIOClient implements Runnable {
    private AsynchronousSocketChannel channel;

    public AIOClient() {
        try {
            channel = AsynchronousSocketChannel.open();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 8001);
        channel.connect(serverAddress, null, new CompletionHandler<Void, Object>() {

            @Override
            public void completed(Void result, Object attachment) {
                channel.write(ByteBuffer.wrap("Hello server".getBytes()));
                final ByteBuffer buffer = ByteBuffer.allocate(1024);
                channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {

                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        buffer.flip();
                        System.out.println(new String(buffer.array()));
                        try {
                            channel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                    }

                });
            }

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

        });
    }

    public static void main(String[] args) throws IOException {

        new Thread(new AIOClient()).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

当然,以上代码是示例代码,目标是为了让您了解JAVA AIO框架的基本使用。所以它还有很多改造的空间,例如,在生产环境下,我们需要记录这个通道上“用户的登录信息”。那么这个需求可以使用JAVA AIO中的“附件”功能进行实现。我们在示例中使用字符串传输内容,但是在正式生产环境下,您会这样用吗?显然是不会的,我们可能会使用json格式等等,所以需要我们做的事情还有很多