NIO簡介

BIO與NIO的區(qū)別

NIO 和 BIO 的區(qū)別主要體現(xiàn)在三個方面:

NIO BIO
基于緩沖區(qū)( Buffer ) 基于流( Stream )
非阻塞 IO 阻塞 IO
選擇器( Selector )
  • 其中,選擇器( Selector )是 NIO 能實(shí)現(xiàn)非阻塞的基礎(chǔ)。
    BIO是阻塞式IO,NIO是同步非阻塞IO。
    這是Socket的模擬,基于BIO,可以看出以下IO流的傳輸是阻塞的,在不考慮多線程的情況下,BIO無法處理并發(fā)。
    如果每個連接處理都開個線程,將會造成很大的資源浪費(fèi),因?yàn)橛泻芏嗟娜怂贿B接不發(fā)信息,這種連接視為浪費(fèi)資源。
/**
 * 模擬一個客戶端
 */
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=null;
        try {
            socket=new Socket("127.0.0.1",8080);
            socket.getOutputStream().write("111".getBytes());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }
}
/**
 * 通過Socket模擬一個客戶端
 */
public class QQServer {
    /**
     * 定義一個字節(jié)數(shù)組用于接收客戶端內(nèi)容
     */
    static byte[] bytes=new byte[1024];

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket=new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            //這是一個阻塞方法,直到有客戶端進(jìn)行通信
            Socket socket=serverSocket.accept();
            //這是一個阻塞方法
            socket.getInputStream().read(bytes);
            System.out.println("讀到內(nèi)容打印");
            String content=new String(bytes);
            System.out.println(content);

        }catch (IOException ex){

        }
    }
}

NIO代碼層原理:邏輯代碼

List<Socket> list=new ArrayList<>();
public void main() throws IOException {
    ServerSocket serverSocket=new ServerSocket();
    serverSocket.bind(new InetSocketAddress(8080));
    /**
     * 設(shè)置服務(wù)器的accept方法為非阻塞方法,防止沒人連接不會一直等待
     */
    serverSocket.setConfig(false);
    while (true){
        //接受一個連接
        Socket socket=serverSocket.accept();
        //如果沒有連接
        if(socket==null){
            System.out.println("沒人來連接");
            //查看前面的socket有沒有客戶端發(fā)送消息
            for(Socket socket1:list){
                int read=socket1.getInputStream().read(bytes);
                //如果前面的客戶端有發(fā)送消息,則處理消息
                if(read!=0){
                    //logic
                }
            }

        }else {//如果有人來連接
            //設(shè)置socket為非阻塞方法
            socket.setConfig(false);
            //將當(dāng)前連接的socket加入List集合
            list.add(socket);
            //同樣處理前面邏輯,看前面的socket是否發(fā)送消息以及現(xiàn)在的socket是否發(fā)送消息。
            for(Socket socket1:list){
                int read=socket1.getInputStream().read(bytes);
                //如果前面的客戶端有發(fā)送消息,則處理消息
                if(read!=0){
                    //logic
                }
            }
        }
    }
}

實(shí)現(xiàn)代碼

public class QQNIOServer {
    /**
     * 字節(jié)數(shù)組存儲讀取的數(shù)據(jù)
     */
    static byte[] bytes=new byte[1024];
    /**
     * 非阻塞Socket對象集合,用于存儲所有的socket
     */
    static List<SocketChannel> list=new ArrayList<>();
    /**
     * 申請一個堆外內(nèi)存,在虛擬機(jī)中也稱為直接內(nèi)存
     */
    static ByteBuffer byteBuffer=ByteBuffer.allocate(512);

    public static void main(String[] args) {
        try {
            //監(jiān)聽開始
            //打開服務(wù)端非阻塞Socket
            ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
            //綁定監(jiān)聽端口號
            serverSocketChannel.bind(new InetSocketAddress(8080));
            //設(shè)置為非阻塞,默認(rèn)為阻塞
            serverSocketChannel.configureBlocking(false);
            while (true){
                //拿到客戶端的socket流
                SocketChannel socketChannel=serverSocketChannel.accept();
                //如果沒有客戶端連接服務(wù)器
                if (socketChannel==null){
                    //處理業(yè)務(wù)邏輯
                    Thread.sleep(500);
                    System.out.println("no conn");
                    //查看前面連接的socket是否有發(fā)送消息
                    for(SocketChannel client:list){
                        int read=client.read(byteBuffer);
                        if(read>0){
                            //該方法表明將從緩存的頭開始讀,并且讀取所讀數(shù)據(jù)的長度,不讀取整個緩存,該方法必有?。?!
                            byteBuffer.flip();
                            System.out.println(byteBuffer.toString());
                        }
                    }

                }else {//當(dāng)?shù)玫揭粋€連接
                    System.out.println("has a conn");
                    socketChannel.configureBlocking(false);
                    list.add(socketChannel);
                    //查看前面連接的socket是否有發(fā)送消息
                    for(SocketChannel client:list){
                        int read=client.read(byteBuffer);
                        if(read>0){
                            //該方法表明將從緩存的頭開始讀,并且讀取所讀數(shù)據(jù)的長度,不讀取整個緩存,該方法必有!?。?                            byteBuffer.flip();
                            System.out.println(byteBuffer.toString());
                        }
                    }
                }
            }
        }catch (Exception e){

        }
    }
}

問題:List集合大部分連接沒用,做了沒必要的輪詢,故此,解決方案是輪詢不能交給我的系統(tǒng),要主動感知有數(shù)據(jù)的socket,在linux上有epoll函數(shù),它會告訴你當(dāng)前來的是什么socket,但在windows上使用的select函數(shù),同樣在內(nèi)核做輪詢。所以Socket的性能與操作系統(tǒng)有關(guān)

基于 Buffer 與基于 Stream

BIO 是面向字節(jié)流或者字符流的,而在 NIO 中,它摒棄了傳統(tǒng)的 IO 流,而是引入 Channel 和 Buffer 的概念:從 Channel 中讀取數(shù)據(jù)到 Buffer 中,或者將數(shù)據(jù)從 Buffer 中寫到 Channel 中。

① 那么什么是基于 Stream呢?

在一般的 Java IO 操作中,我們以流式的方式,順序的從一個 Stream 中讀取一個或者多個字節(jié),直至讀取所有字節(jié)。因?yàn)樗鼪]有緩存區(qū),所以我們就不能隨意改變讀取指針的位置。

② 那么什么是基于 Buffer 呢?參考http://ifeve.com/buffers/

基于 Buffer 就顯得有點(diǎn)不同了。我們在從 Channel 中讀取數(shù)據(jù)到 Buffer 中,這樣 Buffer 中就有了數(shù)據(jù)后,我們就可以對這些數(shù)據(jù)進(jìn)行操作了。并且不同于一般的 Java IO 操作那樣是順序操作,NIO 中我們可以隨意的讀取任意位置的數(shù)據(jù),這樣大大增加了處理過程中的靈活性。

阻塞與非阻塞 IO

Java IO 的各種流是阻塞的 IO 操作。這就意味著,當(dāng)一個線程執(zhí)行讀或?qū)?IO 操作時,該線程會被阻塞,直到有一些數(shù)據(jù)被讀取,或者數(shù)據(jù)完全寫入。

Java NIO 可以讓我們非阻塞的使用 IO 操作。例如:
當(dāng)一個線程執(zhí)行從 Channel 執(zhí)行讀取 IO 操作時,當(dāng)此時有數(shù)據(jù),則讀取數(shù)據(jù)并返回;當(dāng)此時無數(shù)據(jù),則直接返回而不會阻塞當(dāng)前線程。
當(dāng)一個線程執(zhí)行向 Channel 執(zhí)行寫入 IO 操作時,不需要阻塞等待它完全寫入,這個線程同時可以做別的事情。
也就是說,線程可以將非阻塞 IO 的空閑時間用于在其他 Channel 上執(zhí)行 IO 操作。所以,一個單獨(dú)的線程,可以管理多個 Channel 的讀取和寫入 IO 操作。

Selector

Java NIO 引入 Selector ( 選擇器 )的概念,它是 Java NIO 得以實(shí)現(xiàn)非阻塞 IO 操作的最最最關(guān)鍵。
我們可以注冊多個 Channel 到一個 Selector 中。而 Selector 內(nèi)部的機(jī)制,就可以自動的為我們不斷的執(zhí)行查詢( select )操作,判斷這些注冊的 Channel 是否有已就緒的 IO 事件( 例如可讀,可寫,網(wǎng)絡(luò)連接已完成 )。
通過這樣的機(jī)制,一個線程通過使用一個 Selector ,就可以非常簡單且高效的來管理多個 Channel 了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容