溫故知新-java的I/O模型-BIO&NIO&AIO



摘要

通過溫故知新-快速理解Linux網絡IO的回顧,我們瞭解Linux下網絡編程的5種I/O模型&I/O多路複用,接下來回顧一下java中的I/O模型,包括BIO、NIO、AIO,爲下一篇netty做鋪墊。

傳統的BIO編程

傳統的BIO通信模型
模型
問題

  • 該模型最大的問題就是,客戶端的線程個數和客戶端的併發呈1:1的關係,線程切換將滿滿拖垮整個系統;
  • 測試代碼如下
---------- server ---------- 
@Log4j2
public class BioServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9090);
        while (true) {
            log.info("-- serverSocket before accept --");
            Socket socket = serverSocket.accept();
            log.info("-- serverSocket end accept --");
            new Thread(() -> {
                try {
                    // 讀內容
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String readLine = bufferedReader.readLine();
                    log.info("thread:{} client :{}", Thread.currentThread().getName(), readLine);
                } catch (IOException e) {
                    log.error(e);
                }
            }).run();
        }
    }
}

---------- client ---------- 
@Log4j2
public class BioClient {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            Integer tmp = i;
            new Thread(() -> {
                try {
                    Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
                    OutputStream outputStream = socket.getOutputStream();
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.write("client message index: " + tmp);
                    printWriter.flush();
                    log.info("index:{}", tmp);
                } catch (Exception e) {
                    log.error(e);
                }
            }).run();
        }

    }
}

僞異步I/O編程

僞異步

  • 爲了解決線程耗盡的問題,引入了線程池,沒本質區別;

NIO編程

NIO庫是在JDK1.4引進的,彌補了原來同步阻塞I/O的不足,先看一下通信模型,直觀的感受一下;
selector
先了解三個概念:緩衝區(buffer)、通道(channel)、多路複用器(Selector)

  • 緩衝區(buffer)

在NIO厙中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的; 在寫入數據時,寫入到緩衝區中。任何時候訪問NIO中的數據,都是通過緩衝區進行操作。
類型的緩存有很多種,eg:緩存

  • 通道(channel)

channel是一個通道,網絡數據通過channel讀取和寫入;通道是雙向的,流是單項的,流只是在一個方向移動(輸入、輸出),通道是雙工的,可以同時讀寫,這個跟Unix TCP socket也是一致的;

  • Selector
  • 從上面的圖中,最重要的就是Selector,這就是java實現I/O多路複用的核心;
  • 一個Selector可以輪詢多個註冊到Selector的channel,如果某一個channel發送讀寫事件,channel就處於了就緒狀態,就會被Selector輪詢出來,然後通過SelectionKey獲取就是Channel就緒集合,由於JDK使用了epoll()代替了傳統的select輪詢,所以沒有1024的句柄限制。
  • 跟BIO和僞異步IO相比,只用一個線程負責輪詢,就可以接入成千上完的客戶端,所以打好基礎建設多麼的重要!!
  • 流程
    詳細
  • 測試代碼
--------- server -----
@Log4j2
public class NioServer {

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

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(9090));
        serverSocketChannel.configureBlocking(false);

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        new Thread(() -> {
            try {
                while (true) {
                    if (selector.select(1) > 0) {
                        Set<SelectionKey> selectedKeys = selector.selectedKeys();
                        Iterator<SelectionKey> it = selectedKeys.iterator();
                        SelectionKey key = null;
                        while (it.hasNext()) {
                            key = it.next();
                            try {
                                handle(key, selector);
                            } catch (Exception e) {
                                if (key != null) {
                                    key.cancel();
                                    if (key.channel() != null) {
                                        key.channel().close();
                                    }
                                }
                            } finally {
                                it.remove();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }).start();
    }

    private static void handle(SelectionKey key, Selector selector) throws IOException {

        if (key.isValid()) {
            // 處理新接入的請求消息
            if (key.isAcceptable()) {
                log.info("new channel ...");
                // Accept the new connection
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                // Add the new connection to the selector
                sc.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
                // Read the data
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String data = new String(bytes, "UTF-8");
                    log.info("data:{}", data);
                } else if (readBytes < 0) {
                    // 對端鏈路關閉
                    key.cancel();
                    sc.close();
                }
            }
        }
    }
}

AIO編程

NIO 2.0引入了新的異步通的概念,提供了異步文件通道和異步套接字通道的實現。

  • 通過juc的Future表示異步操作的結果
  • 在執行異步操作時傳入channels
  • CompletionHandler接口實現類作爲操作完成的回調。

它是真正的異步非阻塞的IO模型,對應UNIX網絡編程重的事件驅動I/O,不需要Selector對註冊的通道進行輪詢。

  • 測試代碼
@Log4j2
public class AioServer {

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

        AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel
                .open();
        channel.bind(new InetSocketAddress(9090));

        channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(final AsynchronousSocketChannel asynchronousSocketChannel, Void attachment) {
                channel.accept(null, this);

                ByteBuffer buffer = ByteBuffer.allocate(1024);
                asynchronousSocketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result_num, ByteBuffer attachment) {
                        attachment.flip();
                        CharBuffer charBuffer = CharBuffer.allocate(1024);
                        CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
                        decoder.decode(attachment, charBuffer, false);
                        charBuffer.flip();
                        String data = new String(charBuffer.array(), 0, charBuffer.limit());
                        log.info("data:{}", data);
                        try {
                            asynchronousSocketChannel.close();
                        } catch (Exception e) {
                            log.info(e);
                        }
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        log.info("read error");
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                System.out.println("accept error");
            }
        });
        while (true){
            Thread.sleep(1000);
        }
    }
}

幾種IO模型的對比

在這裏插入圖片描述

netty

  • 下一篇就輪到!

參考

-《Netty 權威指南》第二版 – 李林峯
-《netty實戰》–何品


你的鼓勵是我創作的最大動力

打賞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章