java NIO,BIO,AIO理解和學習

java io學習

1 什麼是io

我們常說的io是文件或字符的輸入(input)和輸出(output)

2 BIO,NIO,AIO 有什麼區別
2.1 BIO
什麼是BIO? 
同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。
在活動連接數不是特別高(小於單機1000)的情況下,這種模型是比較不錯的,可以讓每一個連接專注於自己的 I/O 並且編程模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏斗,可以緩衝一些系統處理不了的連接或請求。
但是,當面對十萬甚至百萬級連接的時候,傳統的 BIO 模型是無能爲力的。

一個java BIO實例如下
服務端代碼展示

public class BioServerDemo {

    public static void main(String[] args) {
        int port = 9999;
        try {
            // 創建 bio socket服務
            ServerSocket serverSocket = new ServerSocket();
            // 綁定服務端口號
            serverSocket.bind(new InetSocketAddress(port));
            while (true){
                // 阻塞獲得客戶端socket
               Socket socket = serverSocket.accept();
               // 進入自定義的handle方法,處理客戶端的socket
               new Thread(()-> handle(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 自定義handle方法,處理服務端和客戶端的socket
    private static void handle(Socket socket) {
        byte[] bytes = new byte[1024];
        try {
            // 獲得輸入流,讀出客戶端發送的數據
            int len = socket.getInputStream().read(bytes);
            System.out.println(new String(bytes,0,len));
            // 獲得輸出流,回寫給客戶端數據
            socket.getOutputStream().write("this is server".getBytes());
            socket.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客戶端代碼展示

public class BioClientDemo {

    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 9999;
        try {
            Socket socket = new Socket(host,port);
            // 獲得輸出流,向服務端寫數據
            socket.getOutputStream().write("hello,this is client".getBytes());
            socket.getOutputStream().flush();
            // 獲得輸入流,讀取服務端給客戶端的數據
            byte[] bytes = new byte[1024];
            int len = socket.getInputStream().read(bytes);
            System.out.println(new String(bytes,0,len));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
	優點: 模型簡單,編碼簡單
	缺點:性能較低
	小結:
	1 BIO 主要體現在 獲得客戶端連接socket的時候,在阻塞accept,在一個socket中進行讀寫時,也會一直阻塞,會一直等待這個線程處理完結束
	2 每個連接都會創建一個線程,隨着線程數量的增加,CPU切換線程上下文的消耗也隨之增加,在高過某個閥值後,繼續增加線程,性能不增反降
	3 因爲一個連接創建一個線程,模型比較簡單,適用於客戶端連接教少的場景,可以進行簡單處理
2.2 NIO
什麼是NIO?
NIO 稱爲 Non-Block IO,	它支持面向緩衝的,基於通道的I/O操作方法。
NIO提供了與傳統BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現,兩種通道都支持阻塞和非阻塞兩種模式。
阻塞模式使用就像傳統中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。
含有選擇器,選擇器用於使用單個線程處理多個通道。
它需要較少的線程來處理這些通道。線程之間的切換對於操作系統來說是昂貴的。 
爲了提高系統效率選擇器是有用的。對於低負載、低併發的應用程序,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對於高負載、高併發的(網絡)應用,應使用 NIO 的非阻塞模式來開發


線程模型示例
會有一個線程作爲selector(選擇器)進行處理所有的客戶端,通過事件監聽的形式處理socketChannel

在這裏插入圖片描述

java NIO 單線程實現
服務端代碼

public class NioServerDemo {
    public static void main(String[] args) throws IOException {
        int port = 9999;
        // 創建 nio server
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        System.out.println("server start");
        // 獲得selector
        Selector selector = Selector.open();
        // 把selector和accept事件註冊到server中
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            selector.select();
            // 獲得selector管理的所有事件
            Set<SelectionKey> selectors = selector.selectedKeys();
            // 迭代selector
            Iterator<SelectionKey> selectionKeyIterable = selectors.iterator();
            while (selectionKeyIterable.hasNext()){
                SelectionKey key = selectionKeyIterable.next();
                // 處理每一個 selectionKey
                handle(key);
                // 處理完之後移除
                selectionKeyIterable.remove();
            }
        }
    }

    private static void handle(SelectionKey key){
        // 處理accept事件
        if (key.isAcceptable()){
            // 獲得serversocket channel,處理對客戶端連接的socket
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                serverSocketChannel.configureBlocking(false);
                // 註冊讀事件
                serverSocketChannel.register(key.selector(),SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else if (key.isReadable()){
            // 處理讀事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            byteBuffer.clear();
            int len = 0;
            try {
                len = socketChannel.read(byteBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (len != -1){
                System.out.println(new String(byteBuffer.array(),0,len));
            }
            // 向客戶端寫入數據
            ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
            try {
                socketChannel.write(bufferWrite);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

多線程實現

public class NioThreadPoolServerDemo {

    private ExecutorService executorService = Executors.newFixedThreadPool(20);
    private Selector selector;
    public static void main(String[] args) throws IOException {
        int port = 9999;
        NioThreadPoolServerDemo nioThreadPoolServerDemo = new NioThreadPoolServerDemo();
        nioThreadPoolServerDemo.initServer(port);
        nioThreadPoolServerDemo.listen();
    }
    
    private void initServer(int port) throws IOException {
        // 創建bio 服務
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        // 開啓selector 模型
        this.selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    private void listen() throws IOException {
        while (true){
            selector.select();
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
            if (selectionKeyIterator.hasNext()){
                SelectionKey key = selectionKeyIterator.next();
                selectionKeyIterator.remove();
                // 處理accept事件
                if (key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel sc = server.accept();
                    sc.configureBlocking(false);
                    // 註冊read事件
                    sc.register(this.selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    key.interestOps(SelectionKey.OP_READ);
                    // 有數據讀寫時,交給線程池處理,
                    executorService.execute(new ThreadHandlerChannel(key));
                }
            }
            
            
        }
    }
}
class ThreadHandlerChannel extends Thread{
    private SelectionKey key;
    ThreadHandlerChannel(SelectionKey key){
        this.key = key;
    }

    @Override
    public void run() {
        // 處理讀事件
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        byteBuffer.clear();
        int len = 0;
        try {
            len = socketChannel.read(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (len != -1){
            System.out.println(new String(byteBuffer.array(),0,len));
        }
        // 向客戶端寫入數據
        ByteBuffer bufferWrite = ByteBuffer.wrap("hello,this is server".getBytes());
        try {
            socketChannel.write(bufferWrite);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
	小結
	1 通過選擇器selector進行事件監聽
	2 需要主動把socket事件註冊到選擇器中
2.3 AIO (asynchronius)
	AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。
	異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。
	AIO 是異步IO的縮寫,雖然 NIO 在網絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行爲還是同步的。
	對於 NIO 來說,我們的業務線程是在 IO 操作準備好時,得到通知,接着就由這個線程自行進行 IO 操作,IO操作本身是同步的。
public class AioServerDemo {

    public static void main(String[] args) throws IOException {
        int port = 9999;
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(port));
        // 當有客戶端連接時,accept不再阻塞線程,交給CompletionHandler 進行處理
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverSocketChannel.accept(null,this);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                // 讀數據也不再阻塞執行,讀完數據後交給CompletionHandler處理
                client.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        attachment.flip();
                        client.write(ByteBuffer.wrap("this is server".getBytes()));
                    }
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {}
                });
            }
            @Override
            public void failed(Throwable exc, Object attachment) {}
        });
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

小結:
1 NIO和AIO在linux底層都是用epoll實現
2 windows上AIO用的是系統實現
3 使用場景
1 BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
2 NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
3 AIO方式適用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。
發佈了76 篇原創文章 · 獲贊 30 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章