初識NIO

爲何要使用NIO

NIO(阻塞IO),不論是磁盤IO仍是網絡IO,數據在寫入OutputStream或從InputStream讀取的時候均可能發生阻塞,當發生阻塞,線程會失去CPU的使用權,這個在當前訪問量大和有性能要求的狀況下是不被容許的。若果咱們採用一個客戶端對應一個線程的方式來解決這個問題,可是當須要大量的HTTP長鏈接的時候就會出現問題,好比淘寶的Web旺旺。爲了解決上面的問題,咱們來介紹NIO。

IO和NIO的區別

IO              NIO java

面向流         面向緩衝 算法

阻塞IO        非阻塞IO 服務器

無              選擇器
網絡

NIO

初識NIO

首先引用iteye一個大神的回覆關於NIO的總結:併發

Channel 通道 Buffer 緩衝區 Selector是NIO的三個核心部分。 dom

選擇器其中Channel對應之前的流,Buffer不是什麼新東西,Selector是由於nio可使用異步的非堵塞模式才加入的東西。 之前的流老是堵塞的,一個線程只要對它進行操做,其它操做就會被堵塞,也就至關於水管沒有閥門,你伸手接水的時候,無論水到了沒有,你就都只能耗在接水(流)上。異步

nio的Channel的加入,至關於增長了水龍頭(有閥門),雖然一個時刻也只能接一個水管的水,但依賴輪換策略,在水量不大的時候,各個水管裏流出來的水,均可以獲得妥善接納,這個關鍵之處就是增長了一個接水工,也就Selector,他負責協調,也就是看哪根水管有水了的話,在當前水管的水接到必定程度的時候,就切換一下:臨時關上當前水龍頭,試着打開另外一個水龍頭(看看有沒有水)。socket

當其餘人須要用水的時候,不是直接去接水,而是事前提了一個水桶給接水工,這個水桶就是Buffer。也就是,其餘人雖然也可能要等,但不會在現場等,而是回家等,能夠作其它事去,水接滿了,接水工會通知他們。性能

其實也是很是接近當前社會分工細化的現實,也是統分利用現有資源達到併發效果的一種很經濟的手段,而不是動不動就來個串行處理,雖然那樣是最簡單的,但也是最浪費資源的方式。測試

NIO原理

NIO 有一個主要的類Selector,這個相似一個觀察者,只要咱們把須要探知的socketchannel告訴Selector,咱們接着作別的事情,當有事件發生時,他會通知咱們,傳回一組SelectionKey,咱們讀取這些Key,就會得到咱們剛剛註冊過的socketchannel,而後,咱們從這個Channel中讀取數據,放心,包準可以讀到,接着咱們能夠處理這些數據。

Selector內部原理實際是在作一個對所註冊的channel的輪詢訪問,不斷的輪詢(目前就這一個算法),一旦輪詢到一個channel有所註冊的事情發生,好比數據來了,他就會站起來報告,交出一把鑰匙,讓咱們經過這把鑰匙來讀取這個channel的內容。

NIO例

NIO例子1

RandomAccessFile aFile = new RandomAccessFile("/Users/wjk/myproject/test/IO/src/main/resources/io.txt", "rw");
        //獲取接水工
        FileChannel channel = aFile.getChannel();
        //設置水桶的大小
        ByteBuffer buffer = ByteBuffer.allocate(43);
        //節水工用水桶接水
        int bytesRead = channel.read(buffer);
        if (bytesRead != -1) {
            System.out.println("read: " + bytesRead);
            //接水工準備倒出水桶裏的水(能夠倒出剛纔接的水)
            buffer.flip();
            //水桶是否還有水
            while (buffer.hasRemaining()) {
                System.out.println((char) buffer.get());
            }
            //倒掉剩餘的水
            //compact(將剩餘的水留在水桶中)
            buffer.clear();
            bytesRead = channel.read(buffer);
        }
        aFile.close();

NIO例子2

/**
* 服務器端
**/
public class NIOServer {
    //通道管理器
    private Selector selector;

    /**
     * 得到一個ServerSocket通道,並對該通道作一些初始化的工做
     *
     * @param port 綁定的端口號
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 得到一個ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設置通道爲非阻塞
        serverChannel.configureBlocking(false);
        // 將該通道對應的ServerSocket綁定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 得到一個通道管理器
        this.selector = Selector.open();
        //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,
        //當該事件到達時,selector.select()會返回,若是該事件沒到達selector.select()會一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        System.out.println("服務端啓動成功!");
        // 輪詢訪問selector
        while (true) {
            //當註冊的事件到達時,方法返回;不然,該方法會一直阻塞
            selector.select();
            // 得到selector中選中的項的迭代器,選中的項爲註冊的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重複處理
                ite.remove();
                // 客戶端請求鏈接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key
                            .channel();
                    // 得到和客戶端鏈接的通道
                    SocketChannel channel = server.accept();
                    // 設置成非阻塞
                    channel.configureBlocking(false);

                    //在這裏能夠給客戶端發送信息哦
                    channel.write(ByteBuffer.wrap(new String("向客戶端發送了一條信息").getBytes()));
                    //在和客戶端鏈接成功以後,爲了能夠接收到客戶端的信息,須要給通道設置讀的權限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 得到了可讀的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }

    /**
     * 處理讀取客戶端發來的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 服務器可讀取消息:獲得事件發生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 建立讀取的緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服務端收到信息:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);// 將消息回送給客戶端
    }

    /**
     * 啓動服務端測試
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8001);
        server.listen();
    }
}

/**
* 客戶端
**/
public class NIOClient {
    //通道管理器
    private Selector selector;

    /**
     * 得到一個Socket通道,並對該通道作一些初始化的工做
     * @param ip 鏈接的服務器的ip
     * @param port  鏈接的服務器的端口號
     * @throws IOException
     */
    public void initClient(String ip,int port) throws IOException {
        // 得到一個Socket通道
        SocketChannel channel = SocketChannel.open();
        // 設置通道爲非阻塞
        channel.configureBlocking(false);
        // 得到一個通道管理器
        this.selector = Selector.open();

        // 客戶端鏈接服務器,其實方法執行並無實現鏈接,須要在listen()方法中調
        //用channel.finishConnect();才能完成鏈接
        channel.connect(new InetSocketAddress(ip,port));
        //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 採用輪詢的方式監聽selector上是否有須要處理的事件,若是有,則進行處理
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        // 輪詢訪問selector
        while (true) {
            selector.select();
            // 得到selector中選中的項的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重複處理
                ite.remove();
                // 鏈接事件發生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key
                            .channel();
                    // 若是正在鏈接,則完成鏈接
                    if(channel.isConnectionPending()){
                        channel.finishConnect();

                    }
                    // 設置成非阻塞
                    channel.configureBlocking(false);

                    //在這裏能夠給服務端發送信息哦
                    channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes()));
                    //在和服務端鏈接成功以後,爲了能夠接收到服務端的信息,須要給通道設置讀的權限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 得到了可讀的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }
    /**
     * 處理讀取服務端發來的信息 的事件
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException{
        //和服務端的read方法同樣
    }


    /**
     * 啓動客戶端測試
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost",8001);
        client.listen();
    }

}
相關文章
相關標籤/搜索