BIO、NIO、AIO的理解

先談談我對同步 異步 阻塞 非阻塞的認識:

同步:Client端發送請求,等待結果返回
eg: 小明去買東西,去超市買回來,東西買沒買到,立即知道結果

異步:Client端發送請求,不等待結果返回,(後續等Server端通知等)
eg: 小明打電話讓超市送過來,不知道東西有沒有買到,不管結果

阻塞:Client端發送請求結束後,線程會被掛起
eg: 小明打完電話後一直等東西送過來,不做其他事情/小明去超市買東西不做其他事情

非阻塞:Client端發送請求結束後,線程不會被掛起
eg: 小明打完電話後去上網了,等到東西送過來/小明去超市買東西,同時可以拿個快遞

同步是個過程,阻塞是線程的一種狀態

/////////////////////////////////分割線//////////////////////////////////

BIO 同步阻塞
       同步並阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。 BIO一個連接是一個線程
客戶端:
public class Client {

    public Client() throws IOException {
        init();
    }

    public void init() throws IOException {
        try (Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
             PrintWriter printWriter = new PrintWriter(
                     new OutputStreamWriter(socket.getOutputStream(), "UTF8"))) {
            printWriter.println("this is a test message!!!");
        }
    }

    public static void main(String[] args) throws IOException {
        new Client();
    }
}

服務端:

public class Server {
    public Server() throws IOException {
        init();
    }

    public void init() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(9090, 100,
                InetAddress.getLocalHost())) {
            Socket socket = serverSocket.accept();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(
                    socket.getInputStream(), "UTF8"))) {
                String respone = reader.readLine();
                System.out.println("server receive:" + respone);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Server();
    }

}

NIO 同步非阻塞
       同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。用戶進程也需要時不時的詢問IO操作是否就緒,這就要求用戶進程不停的去詢問,NIO是一個請求一個線程。
客戶端:
public class Client {

    private final Integer PORT = 9090;


    public Client() throws IOException, InterruptedException {
        init();
    }

    public void init() throws IOException, InterruptedException {
        try (Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open()) {
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), PORT));
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            while (true) {
                if (selector.select() > 0) {
                    Iterator<SelectionKey> set = selector.selectedKeys().iterator();
                    while (set.hasNext()) {
                        SelectionKey key = set.next();
                        set.remove();
                        SocketChannel ch = (SocketChannel) key.channel();
                        if (key.isConnectable()) {
                            ch.register(selector, SelectionKey.OP_READ |
                                    SelectionKey.OP_WRITE, new Integer(1));
                            ch.finishConnect();
                        }
                        if (key.isReadable()) {
                            key.attach(new Integer(1));
                            ByteArrayOutputStream output = new ByteArrayOutputStream();
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int len = 0;
                            while ((len = ch.read(buffer)) != 0) {
                                buffer.flip();
                                byte[] bytes = new byte[buffer.remaining()];
                                buffer.get(bytes);
                                output.write(bytes);
                                buffer.clear();
                            }
                            System.out.println("接收到服務端消息:" + new String(output.toByteArray()));
                            output.close();
                        }
                        if (key.isWritable()) {
                            key.attach(new Integer(1));
                            ch.write(ByteBuffer.wrap((("這是一條測試消息")).getBytes()));
                            TimeUnit.SECONDS.sleep(5);
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        new Client();
    }
}

服務端:
public class Server {

    public Server() throws IOException {
        init();
    }

    public void init() throws IOException {
        // 創建多路複用器
        // 創建一個通道
        try (Selector selector = Selector.open();
             ServerSocketChannel socketChannel = ServerSocketChannel.open()) {
            socketChannel.configureBlocking(false);
            //綁定ip和端口
            socketChannel.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(), 9090), 1024);
            //監聽客戶端連接請求
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //阻塞,直到有請求
            System.out.println("等待客戶端鏈接...");
            while (true) {
                selector.select();
                System.out.println("客戶端鏈接進入...");
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        if (key.isValid()) {
                            //處理新接入的請求消息
                            if (key.isAcceptable()) {
                                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                                //通過ServerSocketChannel的accept創建SocketChannel實例
                                //完成該操作意味着完成TCP三次握手,TCP物理鏈路正式建立
                                SocketChannel sc = ssc.accept();
                                //設置爲非阻塞的
                                sc.configureBlocking(false);
                                //註冊爲讀
                                sc.register(selector, SelectionKey.OP_READ);
                            }
                            //讀消息
                            if (key.isReadable()) {
                                SocketChannel sc = (SocketChannel) key.channel();
                                //創建ByteBuffer,並開闢一個1M的緩衝區
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                //讀取請求碼流,返回讀取到的字節數
                                int readBytes = sc.read(buffer);
                                //讀取到字節,對字節進行編解碼
                                if (readBytes > 0) {
                                    //將緩衝區當前的limit設置爲position=0,用於後續對緩衝區的讀取操作
                                    buffer.flip();
                                    //根據緩衝區可讀字節數創建字節數組
                                    byte[] bytes = new byte[buffer.remaining()];
                                    //將緩衝區可讀字節數組複製到新建的數組中
                                    buffer.get(bytes);
                                    String expression = new String(bytes, "UTF-8");
                                    System.out.println("服務器收到消息>>>:" + expression);
                                    //發送應答消息
                                    doWrite(sc, "這是一條返回信息");
                                }
                                //鏈路已經關閉,釋放資源
                                else if (readBytes < 0) {
                                    key.cancel();
                                    sc.close();
                                }
                            }
                        }
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * 異步發送應答消息
     */
    private void doWrite(SocketChannel channel, String response) throws IOException {
        //將消息編碼爲字節數組
        byte[] bytes = response.getBytes();
        //根據數組容量創建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //將字節數組複製到緩衝區
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //發送緩衝區的字節數組
        channel.write(writeBuffer);
    }

    public static void main(String[] args) throws IOException {
        new Server();
    }

AIO 異步非阻塞
       在此種模式下,用戶進程只需要發起一個IO操作然後立即返回,等IO操作真正的完成以後,應用程序會得到IO操作完成的通知,此時用戶進程只需要對數據進行處理就好了,不需要進行實際的IO讀寫操作,因爲真正的IO讀取或者寫入操作已經由內核完成了。  AIO是一個有效請求一個線程 。
客戶端:

服務端:

public class Client {


    public Client() throws IOException, InterruptedException {
        init();
    }

    public void init() throws IOException, InterruptedException {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //連接服務
        socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 9090), 
                null, new CompletionHandler<Void, Void>() {
            final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);

            @Override
            public void completed(Void result, Void attachment) {
                //連接成功後, 異步調用OS向服務器寫一條消息
                try {
                    ByteBuffer buffer = Charset.forName("UTF-8").
                            newEncoder().encode(CharBuffer.wrap("this is test aio msg"));
                    socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            if (buffer.hasRemaining()) {
                                socketChannel.write(buffer, buffer, this);
                            }
                        }
                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });
                } catch (CharacterCodingException e) {
                    e.printStackTrace();
                }
                readBuffer.clear();
                //異步調用OS讀取服務器發送的消息
                socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
                    @Override
                    public void completed(Integer result, Object attachment) {
                        try {
                            //異步讀取完成後處理
                            if (result > 0) {
                                readBuffer.flip();
                                CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
                                CharBuffer charBuffer = decoder.decode(readBuffer);
                                String answer = charBuffer.toString();
                                System.out.println(Thread.currentThread().getName() + "---" + answer);
                                readBuffer.clear();
                                socketChannel.read(readBuffer, null, this);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    /**
                     * 讀取失敗處理
                     * @param exc
                     * @param attachment
                     */
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("client read failed: " + exc);
                    }
                });
            }

            /**
             * 連接失敗處理
             * @param exc
             * @param attachment
             */
            @Override
            public void failed(Throwable exc, Void attachment) {
                System.out.println("client connect to server failed: " + exc);
            }
        });
        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);

    }
服務端:


public class Server {

    public Server() throws IOException {
        init();
    }

    public void init() throws IOException {
        try (AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLocalHost(), 9090), 100)) {
            serverChannel.accept(this, new AcceptHandler());
            TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * accept到一個請求時的回調
     */
    private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Server> {
        @Override
        public void completed(final AsynchronousSocketChannel client, Server attachment) {
            try {
                System.out.println("遠程地址:" + client.getRemoteAddress());
                //tcp各項參數
                client.setOption(StandardSocketOptions.TCP_NODELAY, true);
                client.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
                client.setOption(StandardSocketOptions.SO_RCVBUF, 1024);

                if (client.isOpen()) {
                    System.out.println("client.isOpen:" + client.getRemoteAddress());
                    final ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.clear();
                    client.read(buffer, client, new ReadHandler(buffer));
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

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


    /**
     * Read到請求數據的回調
     */
    private class ReadHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {

        private ByteBuffer buffer;

        public ReadHandler(ByteBuffer buffer) {
            this.buffer = buffer;
        }

        @Override
        public void completed(Integer result, AsynchronousSocketChannel attachment) {
            try {
                if (result < 0) {
                } else if (result == 0) {
                    System.out.println("空數據");
                } else {
                    // 讀取請求,處理客戶端發送的數據
                    buffer.flip();
                    CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
                    CharBuffer charBuffer = decoder.decode(buffer);
                    System.out.println("獲取的數據:" + charBuffer.toString());
                    //響應操作,服務器響應結果
                    buffer.clear();
                    String res = "我給你回了一條數據";
                    buffer = ByteBuffer.wrap(res.getBytes());
                    attachment.write(buffer, attachment, new WriteHandler(buffer));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

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

    /**
     * Write響應完請求的回調
     */
    private class WriteHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
        private ByteBuffer buffer;

        public WriteHandler(ByteBuffer buffer) {
            this.buffer = buffer;
        }

        @Override
        public void completed(Integer result, AsynchronousSocketChannel attachment) {
            buffer.clear();
        }

        @Override
        public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
            exc.printStackTrace();
        }
    }
 BIO、NIO、AIO適用場景分析: 
    BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。 
    NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。 
    AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。


               http://ifeve.com/selectors/

代碼github:https://github.com/krauser1991/socket

發佈了36 篇原創文章 · 獲贊 11 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章