AIO入門實例

AIO入門實例

摘要:閱讀《Netty權威指南》筆記
AIO概念:
AIO即JDK7開始引入的NIO,提供了異步文件通道和異步到街子通道的實現。異步通道提供兩種方式操作結果:

  1. 通過java.util.concurrent.Future類來表示異步操作的結果;
  2. 在執行異步操作時傳入一個java.nio.channels.CompletionHandler接口的實現類作爲操作完成的回調(本例子主要使用這個方法)。

AIO創建的TimeServer

public class TimeServer {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        AsyncTimeServerHander timeServerHander = new AsyncTimeServerHander(port);
        new Thread(timeServerHander, "AIO-AsyncTimeServerHandler-001").start();
    }
}

很簡單,就是初始化一個線程,接下來看AsyncTimeServerHander 的實現代碼:

public class AsyncTimeServerHander implements Runnable {

    private int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    /**
     * 構造方法,創建一個異步的服務端通道AsynchronousServerSocketChannel,調用它的bind方法綁定監
     * 聽端口,綁定成功則打印提示到輸入臺
     *
     * @param port
     */
    public AsyncTimeServerHander(int port) {
        this.port = port;
        try {
            asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
            System.out.println("The time server is start in port " + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //阻塞當前線程,防止服務端異常退出
        latch = new CountDownLatch(1);
        doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 連接客戶端的方法,採用的CompletionHandler方法,接收AcceptCompletionHandler作爲採用的CompletionHandler方
     * 法實例
     */
    public void doAccept() {
        asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
    }
}

CompletionHandler接口有兩個方法:

  1. public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment)
  2. public void failed(Throwable exc, AsyncTimeServerHander attachment)

根據方法名很容易就能理解兩個方法的執行時間。
接下來看整個AcceptCompletionHandler 的代碼:

public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHander> {
    @Override
    public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment) {
        /**
         * 接收客戶端請求,因爲AsynchronousServerSocketChannel可以接收成千上萬的客戶端,
         * 所以回調AsyncTimeServerHander中asynchronousServerSocketChannel.accept方法,
         * 讓新的客戶端繼續接入,最終形成一個循環
         */
        attachment.asynchronousServerSocketChannel.accept(attachment, this);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        /**異步讀操作
         * 第一個buffer:接收緩衝區
         * 第二個buffer:異步Channel攜帶的附件,通知回調的時候作爲入參使用(我的理解是當該Channel繼續被調用時的參數)
         * CompletionHandler:接收通知回調的業務Handler,這裏的實現類是ReadCompletionHandler
         */
        result.read(buffer, buffer, new ReadCompletionHandler(result));
    }

    @Override
    public void failed(Throwable exc, AsyncTimeServerHander attachment) {
        attachment.latch.countDown();
    }
}

AcceptCompletionHandler 就可以看出,主要是通過不斷的回調來實現非阻塞,同時不會對像NIO那樣需要不斷的判斷連接的狀態去根據具體的狀態分配Handler。
繼續往下看ReadCompletionHandler

public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {

    private AsynchronousSocketChannel channel;

    /**
     * 構造方法:將AsynchronousSocketChannel作爲構造方法的參數傳入,作爲成員變量使用,
     * 主要用於半包消息和發送應答
     *
     * @param channel
     */
    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        if (channel != null) {
            this.channel = channel;
        }
    }

    /**
     * 讀取到消息的處理
     *
     * @param result
     * @param attachment
     */
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        attachment.flip();
        byte[] body = new byte[attachment.remaining()];
        attachment.get(body);
        try {
            String req = new String(body, "UTF-8");
            System.out.println("The time server receive order : " + req);
            //消息判斷,如果是正確的消息,調用doWrite發送當前消息到客戶端
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ?
                    new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
            doWrite(currentTime);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發送消息到客戶端
     *
     * @param currentTime
     */
    private void doWrite(String currentTime) {
        if (currentTime != null && currentTime.trim().length() > 0) {
            byte[] bytes = currentTime.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    //遞歸調用,如果沒有發送完成,繼續發送
                    if (attachment.hasRemaining()) {
                        channel.write(attachment, attachment, this);
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        //ingore on close
                    }
                }
            });
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ReadCompletionHandler 的主要功能已經在代碼裏註釋好了,理解起來也不是很難

總結

  1. AIO使用起來邏輯上很好理解,但是因爲有很多的匿名函數,導致代碼看起來很複雜;
  2. AIO非阻塞的思想其實和NIO一樣,都是通過不斷輪詢AsynchronousServerSocketChannelaccept 方法獲取連接,分配對應的Handler 去處理業務;
  3. demo很簡單,重要的是對代碼的理解以及熟練使用API。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章