1. 程式人生 > >【JAVA NIO】java NIO

【JAVA NIO】java NIO

        本文是博主深入學習Netty前的一些鋪墊,之前只是使用Netty,用的很粗暴,導包,上網找個DEMO就直接用,對Netty中的元件瞭解並不深入。

        於是再此總結下基礎,並對一些核心元件作如下記錄:

 

1. 概述

java NIO核心的APIChannel,Buffer 和 Selector 

所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 資料可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。

使用Selector,得向Selector註冊Channel,然後呼叫它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,執行緒就可以處理這些事件,事件的例子有如新連線進來,資料接收等。

 

2.詳述

摘抄以下詳細說明,摘抄地址:https://blog.csdn.net/anxpp/article/details/51512200

    JDK 1.4中的java.nio.*包中引入新的Java I/O庫,其目的是提高速度。實際上,“舊”的I/O包已經使用NIO重新實現過,即使我們不顯式的使用NIO程式設計,也能從中受益。速度的提高在檔案I/O和網路I/O中都可能會發生,但本文只討論後者。

    2.1、簡介
    NIO我們一般認為是New I/O(也是官方的叫法),因為它是相對於老的I/O類庫新增的(其實在JDK 1.4中就已經被引入了,但這個名詞還會繼續用很久,即使它們在現在看來已經是“舊”的了,所以也提示我們在命名時,需要好好考慮),做了很大的改變。但民間跟多人稱之為Non-block I/O,即非阻塞I/O,因為這樣叫,更能體現它的特點。而下文中的NIO,不是指整個新的I/O庫,而是非阻塞I/O。

    NIO提供了與傳統BIO模型中的Socket和ServerSocket相對應的SocketChannel和ServerSocketChannel兩種不同的套接字通道實現。

    新增的著兩種通道都支援阻塞和非阻塞兩種模式。

    阻塞模式使用就像傳統中的支援一樣,比較簡單,但是效能和可靠性都不好;非阻塞模式正好與之相反。

    對於低負載、低併發的應用程式,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對於高負載、高併發的(網路)應用,應使用NIO的非阻塞模式來開發。

    下面會先對基礎知識進行介紹。

    2.2、緩衝區 Buffer
    Buffer是一個物件,包含一些要寫入或者讀出的資料。

    在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的;在寫入資料時,也是寫入到緩衝區中。任何時候訪問NIO中的資料,都是通過緩衝區進行操作。

    緩衝區實際上是一個數組,並提供了對資料結構化訪問以及維護讀寫位置等資訊。

    具體的快取區有這些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他們實現了相同的介面:Buffer。

    2.3、通道 Channel
    我們對資料的讀取和寫入要通過Channel,它就像水管一樣,是一個通道。通道不同於流的地方就是通道是雙向的,可以用於讀、寫和同時讀寫操作。

    底層的作業系統的通道一般都是全雙工的,所以全雙工的Channel比流能更好的對映底層作業系統的API。

    Channel主要分兩大類:

    SelectableChannel:使用者網路讀寫
    FileChannel:用於檔案操作
    後面程式碼會涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子類。

    2.4、多路複用器 Selector
    Selector是Java  NIO 程式設計的基礎。

    Selector提供選擇已經就緒的任務的能力:Selector會不斷輪詢註冊在其上的Channel,如果某個Channel上面發生讀或者寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。

    一個Selector可以同時輪詢多個Channel,因為JDK使用了epoll()代替傳統的select實現,所以沒有最大連線控制代碼1024/2048的限制。所以,只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬的客戶端。

 

3.Java NIO建立的過程

         以下是java 提供的API 建立NIO server處理類的過程,各個API之間處理過程如下,可以看出比傳統BIO Socket程式設計要多出很多物件,建立過程也比較複雜。

博主簡化了關鍵程式碼用於方便閱讀,完整程式碼連結:https://blog.csdn.net/the_fool_/article/details/80700558

 

//建立選擇器
        Selector selector = Selector.open();
        //開啟ServerSocketChannel,監聽客戶端連線
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //如果為 true,則此通道將被置於阻塞模式;如果為 false,則此通道將被置於非阻塞模式
        serverChannel.configureBlocking(false);
        //繫結埠 backlog設為1024
        serverChannel.socket().bind(new InetSocketAddress(port), 1024);
        //監聽客戶端連線請求
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            //無論是否有讀寫事件發生,selector每隔1s被喚醒一次
            selector.select(1000);
            //返回已此通道已準備就緒的鍵集
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            SelectionKey key = null;
            //處理所有的
            while (it.hasNext()) {
                key = it.next();
                it.remove();
                //處理Key中的資訊
                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("SERVER GET MSSG:" + expression);
                            //處理資料
                            String result = "server got ";
                            //傳送應答訊息
                            responseToClient(sc, result);
                        }

                        //鏈路已經關閉,釋放資源

                    }
                }
            }
        }