Java NIO原理與簡單實現

Java NIO 簡介
JAVA NIO有兩種解釋:一種叫非阻塞IO(Non-blocking I/O),另一種也叫新的IO(New I/O),其實是同一個概念。它是一種同步非阻塞的I/O模型,也是I/O多路複用的基礎,已經被越來越多地應用到大型應用服務器,成爲解決高併發與大量連接、I/O處理問題的有效方式。

NIO是一種基於通道和緩衝區的I/O方式,它可以使用Native函數庫直接分配堆外內存(區別於JVM的運行時數據區),然後通過一個存儲在java堆裏面的DirectByteBuffer對象作爲這塊內存的直接引用進行操作。這樣能在一些場景顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據。

Java NIO組件
NIO主要有三大核心部分:Channel(通道),Buffer(緩衝區), Selector(選擇器)。傳統IO是基於字節流和字符流進行操作(基於流),而NIO基於Channel和Buffer(緩衝區)進行操作,數據總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector(選擇區)用於監聽多個通道的事件(比如:連接打開,數據到達)。因此,單個線程可以監聽多個數據通道。

Buffer
Buffer(緩衝區)是一個用於存儲特定基本類型數據的容器。除了boolean外,其餘每種基本類型都有一個對應的buffer類。Buffer類的子類有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。

Channel
Channel(通道)表示到實體,如硬件設備、文件、網絡套接字或可以執行一個或多個不同 I/O 操作(如讀取或寫入)的程序組件的開放的連接。Channel接口的常用實現類有FileChannel(對應文件IO)、DatagramChannel(對應UDP)、SocketChannel和ServerSocketChannel(對應TCP的客戶端和服務器端)。Channel和IO中的Stream(流)是差不多一個等級的。只不過Stream是單向的,譬如:InputStream, OutputStream.而Channel是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。

Selector
Selector(選擇器)用於監聽多個通道的事件(比如:連接打開,數據到達)。因此,單個的線程可以監聽多個數據通道。即用選擇器,藉助單一線程,就可對數量龐大的活動I/O通道實施監控和維護。

注:
寫就緒相對有一點特殊,一般來說,你不應該註冊寫事件。寫操作的就緒條件爲底層緩衝區有空閒空間,而寫緩衝區絕大部分時間都是有空閒空間的,所以當你註冊寫事件後,寫操作一直是就緒的,選擇處理線程全佔用整個CPU資源。所以,只有當你確實有數據要寫時再註冊寫操作,並在寫完以後馬上取消註冊。

基於阻塞式I/O的多線程模型中,Server爲每個Client連接創建一個處理線程,每個處理線程阻塞式等待可能達到的數據,一旦數據到達,則立即處理請求、返回處理結果並再次進入等待狀態。由於每個Client連接有一個單獨的處理線程爲其服務,因此可保證良好的響應時間。但當系統負載增大(併發請求增多)時,Server端需要的線程數會增加,對於操作系統來說,線程之間上下文切換的開銷很大,而且每個線程都要佔用系統的一些資源(如內存)。因此,使用的線程越少越好。

但是,現代的操作系統和CPU在多任務方面表現的越來越好,所以多線程的開銷隨着時間的推移,變得越來越小了。實際上,如果一個CPU有多個內核,不使用多任務可能是在浪費CPU能力。

傳統的IO處理方式,一個線程處理一個網絡連接

這裏寫圖片描述
NIO處理方式,一個線程可以管理過個網絡連接

這裏寫圖片描述
NIO服務器端如何實現非阻塞?
服務器上所有Channel需要向Selector註冊,而Selector則負責監視這些Socket的IO狀態(觀察者),當其中任意一個或者多個Channel具有可用的IO操作時,該Selector的select()方法將會返回大於0的整數,該整數值就表示該Selector上有多少個Channel具有可用的IO操作,並提供了selectedKeys()方法來返回這些Channel對應的SelectionKey集合(一個SelectionKey對應一個就緒的通道)。正是通過Selector,使得服務器端只需要不斷地調用Selector實例的select()方法即可知道當前所有Channel是否有需要處理的IO操作。注:java NIO就是多路複用IO,jdk7之後底層是epoll模型。

Java NIO的簡單實現
服務端

package com.github.sources.network.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *
 */
public class NioServer {

    private int port;
    private Selector selector;
    private ExecutorService service = Executors.newFixedThreadPool(5);

    public static void main(String[] args){
        new NioServer(8080).start();
    }

    public NioServer(int port) {
        this.port = port;
    }

    public void init() {
        ServerSocketChannel ssc = null;
        try {
            ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(port));
            selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NioServer started ......");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
        }
    }

    public void accept(SelectionKey key) {
        try {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            sc.register(selector, SelectionKey.OP_READ);
            System.out.println("accept a client : " + sc.socket().getInetAddress().getHostName());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        this.init();
        while (true) {
            try {
                int events = selector.select();
                if (events > 0) {
                    Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                    while (selectionKeys.hasNext()) {
                        SelectionKey key = selectionKeys.next();
                        selectionKeys.remove();
                        if (key.isAcceptable()) {
                            accept(key);
                        } else {
                            service.submit(new NioServerHandler(key));
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static class NioServerHandler implements Runnable{

        private SelectionKey selectionKey;

        public NioServerHandler(SelectionKey selectionKey) {
            this.selectionKey = selectionKey;
        }

        @Override
        public void run() {
            try {
                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer);
                    buffer.flip();
                    System.out.println("收到客戶端"+socketChannel.socket().getInetAddress().getHostName()+"的數據:"+new String(buffer.array()));
                    //將數據添加到key中
                    ByteBuffer outBuffer = ByteBuffer.wrap(buffer.array());
                    socketChannel.write(outBuffer);// 將消息回送給客戶端
                    selectionKey.cancel();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客戶端

package com.github.sources.network.nio;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 *
 */
public class NioClient {
    private static final String host = "127.0.0.1";
    private static final int port = 8080;
    private Selector selector;

    public static void main(String[] args){
        for (int i=0;i<3;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    NioClient client = new NioClient();
                    client.connect(host, port);
                    client.listen();
                }
            }).start();
        }
    }

    public void connect(String host, int port) {
        try {
            SocketChannel sc = SocketChannel.open();
            sc.configureBlocking(false);
            this.selector = Selector.open();
            sc.register(selector, SelectionKey.OP_CONNECT);
            sc.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listen() {
        while (true) {
            try {
                int events = selector.select();
                if (events > 0) {
                    Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                    while (selectionKeys.hasNext()) {
                        SelectionKey selectionKey = selectionKeys.next();
                        selectionKeys.remove();
                        //連接事件
                        if (selectionKey.isConnectable()) {
                            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                            if (socketChannel.isConnectionPending()) {
                                socketChannel.finishConnect();
                            }

                            socketChannel.configureBlocking(false);
                            socketChannel.register(selector, SelectionKey.OP_READ);
                            socketChannel.write(ByteBuffer.wrap(("Hello this is " + Thread.currentThread().getName()).getBytes()));
                        } else if (selectionKey.isReadable()) {
                            SocketChannel sc = (SocketChannel) selectionKey.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            sc.read(buffer);
                            buffer.flip();
                            System.out.println("收到服務端的數據:"+new String(buffer.array()));
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 本文轉自:https://blog.csdn.net/u013857458/article/details/82424104

 

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