Java中BIO和NIO


同步/異步、阻塞/非阻塞概念

同步異步

同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication)

同步:在發出一個調用時,在沒有得到結果之前,該調用就不返回。一旦調用返回,就得到返回值了。調用者主動等待這個調用的結果。
異步:調用在發出之后就直接返回了,沒有立刻得到返回結果。在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用。

I/O

通常來說,IO操作包括:對硬盤的讀寫、對socket的讀寫以及外設的讀寫,並且需要進行用戶空間和內核空
間的區分(用戶空間就是普通的用戶進程,內核空間就是內核進程,只有內核空間才可以直接范圍磁盤等物理
I/O設備)

用戶空間產生一個讀請求,請求再轉交由內核空間執行
1. 內核檢查讀取的數據是否就緒
2. 如果就緒,內核將數據從內核空間復制到用戶空間(內存上拷貝)

阻塞I/O與非阻塞I/O

阻塞I/O:內核在檢查數據未就緒時,會一直等待,直到數據就緒
非阻塞I/O:如果數據沒有就緒,則會返回一個標志信息告知用戶線程當前要讀的數據沒有就緒

它們的區別在於I/O的第一階段,阻塞是選擇等待,非阻塞是返回一個標志信息

那么非阻塞I/O的優勢在哪里呢?使用阻塞I/O處理網絡連接時,有10000個連接就要開10000個線程,無論
有沒有數據到來,處理某一連接的線程必須“忠實地阻塞”。而非阻塞I/O就不需要這樣,它可以維護一個1000
個線程的線程池,當有數據就緒時,啟動一個線程去接受數據,當沒有數據時,線程不需要等待,直接就可以
回到池中,等待被調度到去接受其它連接。因此非阻塞I/O非常適合連接多但傳輸的數據內容不大的情況,
果連接少數據多,阻塞I/O更容易編程

同步I/O和異步I/O

事實上,同步IO和異步IO模型是針對用戶線程和內核的交互來說的,即數據是否就緒的消息傳遞機制

同步IO:當用戶發出IO請求操作之后,如果數據沒有就緒,需要通過用戶線程或者內核不斷地去輪詢數據是否
就緒,當數據就緒時,再將數據從內核拷貝到用戶線程

異步IO:只有IO請求操作的發出是由用戶線程來進行的,內核自動完成檢查數據是否就緒和將數據拷貝
到用戶空間的過程,然后發送通知告知用戶線程IO操作已經完成。

BIO、NIO、AIO

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

NIO:同步非阻塞式IO,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。 

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

BIO

阻塞IO,一個請求對應一個線程,線程是阻塞的。

缺點:

阻塞式IO、彈性伸縮能力強、多線程消耗資源。

NIO

每個客戶端請求都會注冊到Selector(多路復用器)

優點:

非阻塞式IO模型、彈性伸縮能力強、單線陳節省資源。 

同步非阻塞式IO,關鍵是采用了事件驅動的思想實現了一個多路轉換器。 

NIO與BIO最大的區別就是只需要開啟一個線程就可以處理來自多個客戶端的IO事件,實現原理就是多路復用器,可以監聽來自多個客戶端的IO事件:
A. 若服務端監聽到客戶端連接請求,便為其建立通信套接字(java中就是通道),然后返回繼續監聽,若同時有多個客戶端連接請求到來也可以全部收到,依次為它們都建立通信套接字。
B. 若服務端監聽到來自已經創建了通信套接字的客戶端發送來的數據,就會調用對應接口處理接收到的數據,若同時有多個客戶端發來數據也可以依次進行處理。
C. 監聽多個客戶端的連接請求和接收數據請求同時還能監聽自己是否有數據要發送。


總之就是在一個線程中就可以調用多路復用接口(java中是select)阻塞同時監聽來自多個客戶端的IO請求,一旦有收到IO請求就調用對應函數處理。 
一旦有請求到來(不管是幾個同時到還是只有一個到),都會調用對應IO處理函數處理,所以:

(1)NIO適合處理連接數目特別多,但是連接比較短(輕操作)的場景Jetty,Mina,ZooKeeper等都是基於java nio實現

(2)BIO方式適用於連接數目比較小且固定的場景,這種方式對服務器資源要求比較高,並發局限於應用中。

Java的NIO代碼

相關鏈接:java的socket編程(BIO)

this.selector.select(); 在這里會阻塞,無論是客戶端連接還是客戶端發送數據還是客戶端關閉,這里都會觸發。雖然這里是單線程,但是底層處理用到了線程池。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 阻塞點this.selector.select();
 * 輪詢器雖然是一個線程內部也是線程池
 */
public class NioSocket {
    private Selector selector;   //通道管理器(管理器)

    /**
     * 初始化Channel並綁定端口
     * @param port
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);  //非阻塞
        serverChannel.socket().bind(new InetSocketAddress(port));

        this.selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服務器已啟動...");
    }

    /**
     * 監聽輪詢器
     * @throws IOException
     */
    public void listenSelector() throws IOException {
        //輪詢監聽selector
        while (true){
            //等待客戶連接
            //select模型,多路復用
            //this.selector.select();   //在這里會阻塞,無論是連接還是客戶端發送數據還是客戶端關閉,這里都會觸發
            this.selector.selectNow();   //這里不阻塞會立即執行
            Iterator<SelectionKey> iteKey = this.selector.selectedKeys().iterator();
            while (iteKey.hasNext()){
                SelectionKey key = iteKey.next();
                iteKey.remove();  //移除,防止重復處理
                //處理請求
                handler(key);
            }
        }
    }

    /**
     * 處理客戶端請求
     * @param key
     */
    private void handler(SelectionKey key) throws IOException {
        if (key.isAcceptable()){  //處理連接請求
            //處理客戶端連接請求事件
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = serverChannel.accept();
            //接受客戶端發送的信息時,需要給通道設置讀權限
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ);
        }else if(key.isReadable()){   //處理讀請求
            //處理讀事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int readData = socketChannel.read(buffer);
            if (readData>0){
                String info  = new String(buffer.array(),"GBK").trim();
                System.out.println("服務端收到數據: "+Thread.currentThread()+info);
            }else {
                System.out.println("客戶端關閉了...");
                key.cancel();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NioSocket nio = new NioSocket();
        nio.initServer(8888);
        nio.listenSelector();
    }
}

 

參考

http://m.elecfans.com/article/632834.html

https://www.cnblogs.com/IUbanana/p/7245980.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM