精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面試官:BIO、NIO、AIO之間有什么區(qū)別?

開發(fā) 架構(gòu)
AIO 了,全稱 Asynchronous I/O,可以理解為異步 IO,也被稱為 NIO 2,在 Java 7 中引入,它是異步非阻塞的 IO 模型。異步 IO 是基于事件回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會直接返回,不會堵塞在那里,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)的線程進(jìn)行后續(xù)的操作。

一、簡介

在計算機(jī)中,IO 傳輸數(shù)據(jù)有三種工作方式,分別是: BIO、NIO、AIO。

在講解 BIO、NIO、AIO 之前,我們先來回顧一下這幾個概念:同步與異步,阻塞與非阻塞。

同步與異步的區(qū)別

  • 同步就是發(fā)起一個請求后,接受者未處理完請求之前,不返回結(jié)果。
  • 異步就是發(fā)起一個請求后,立刻得到接受者的回應(yīng)表示已接收到請求,但是接受者并沒有處理完,接受者通常依靠事件回調(diào)等機(jī)制來通知請求者其處理結(jié)果。

阻塞和非阻塞的區(qū)別

  • 阻塞就是請求者發(fā)起一個請求,一直等待其請求結(jié)果返回,也就是當(dāng)前線程會被掛起,無法從事其他任務(wù),只有當(dāng)條件就緒才能繼續(xù)。
  • 非阻塞就是請求者發(fā)起一個請求,不用一直等著結(jié)果返回,可以先去干其他事情,當(dāng)條件就緒的時候,就自動回來。

而我們要講的 BIO、NIO、AIO 就是同步與異步、阻塞與非阻塞的組合。

  • BIO:同步阻塞 IO;
  • NIO:同步非阻塞 IO;
  • AIO:異步非阻塞 IO;

不同的工作方式,帶來的傳輸效率是不一樣的,下面我們以網(wǎng)絡(luò) IO 為例,一起看看不同的工作方式下,彼此之間有何不同。

二、BIO

BIO 俗稱同步阻塞 IO,是一種非常傳統(tǒng)的 IO 模型,也是最常用的網(wǎng)絡(luò)數(shù)據(jù)傳輸處理方式,優(yōu)點(diǎn)就是編程簡單,但是缺點(diǎn)也很明顯,I/O 傳輸性能一般比較差,CPU 大部分處于空閑狀態(tài)。

采用 BIO 通信模型的服務(wù)端,通常由一個獨(dú)立的 Acceptor 線程負(fù)責(zé)監(jiān)聽所有客戶端的連接,當(dāng)服務(wù)端接受到多個客戶端的請求時,所有的客戶端只能排隊等待服務(wù)端一個一個的處理。

BIO 通信模型圖如下!

圖片圖片

一般在服務(wù)端通過while(true)循環(huán)中會調(diào)用accept() 方法監(jiān)聽客戶端的連接,一旦接收到一個連接請求,就可以建立通信套接字進(jìn)行讀寫操作,此時不能再接收其他客戶端連接請求,只能等待同當(dāng)前連接的客戶端的操作執(zhí)行完成。

服務(wù)端操作,樣例程序如下:

public class BioServerTest {

    public static void main(String[] args) throws IOException {
        //初始化服務(wù)端socket并且綁定 8080 端口
        ServerSocket serverSocket = new ServerSocket(8080);
        //循環(huán)監(jiān)聽客戶端請求
        while (true){
            try {
                //監(jiān)聽客戶端請求
                Socket socket = serverSocket.accept();

                //將字節(jié)流轉(zhuǎn)化成字符流,讀取客戶端輸入的內(nèi)容
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //讀取一行數(shù)據(jù)
                String str = bufferedReader.readLine();
                //打印客戶端發(fā)送的信息
                System.out.println("服務(wù)端收到客戶端發(fā)送的信息:" + str);

                //向客戶端返回信息,將字符轉(zhuǎn)化成字節(jié)流,并輸出
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                printWriter.println("hello,我是服務(wù)端,已收到消息");

                // 關(guān)閉流
                bufferedReader.close();
                printWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客戶端操作,樣例程序如下:

public class BioClientTest {

    public static void main(String[] args) {
        //創(chuàng)建10個線程,模擬10個客戶端,同時向服務(wù)端發(fā)送請求
        for (int i = 0; i < 10; i++) {
            final int j = i;//定義變量
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        //通過IP和端口與服務(wù)端建立連接
                        Socket socket =new Socket("127.0.0.1",8080);
                        //將字符流轉(zhuǎn)化成字節(jié)流,并輸出
                        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                        String str="Hello,我是" + j + "個,客戶端!";
                        printWriter.println(str);

                        //從輸入流中讀取服務(wù)端返回的信息,將字節(jié)流轉(zhuǎn)化成字符流
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        //讀取內(nèi)容
                        String result = bufferedReader.readLine();
                        //打印服務(wù)端返回的信息
                        System.out.println("客戶端發(fā)送請求內(nèi)容:" + str + " -> 收到服務(wù)端返回的內(nèi)容:" + result);

                        // 關(guān)閉流
                        bufferedReader.close();
                        printWriter.close();
                        // 關(guān)閉socket
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

最后,依次啟動服務(wù)端、客戶端,看看控制臺輸出情況如何。

服務(wù)端控制臺結(jié)果如下:

服務(wù)端收到客戶端發(fā)送的信息:Hello,我是8個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是9個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是7個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是5個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是4個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是3個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是6個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是2個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是1個,客戶端!
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是0個,客戶端!

客戶端控制臺結(jié)果如下:

客戶端發(fā)送請求內(nèi)容:Hello,我是8個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是9個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是7個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是5個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是4個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是3個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是6個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是2個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是1個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息
客戶端發(fā)送請求內(nèi)容:Hello,我是0個,客戶端! -> 收到服務(wù)端返回的內(nèi)容:hello,我是服務(wù)端,已收到消息

隨著客戶端的請求次數(shù)越來越多,可能需要排隊的時間會越來越長,因此是否可以在服務(wù)端,采用多線程編程進(jìn)行處理呢?

答案是,可以的!

下面我們對服務(wù)端的代碼進(jìn)行改造,服務(wù)端多線程操作,樣例程序如下:

public class BioServerTest {

    public static void main(String[] args) throws IOException {
        //初始化服務(wù)端socket并且綁定 8080 端口
        ServerSocket serverSocket = new ServerSocket(8080);
        //循環(huán)監(jiān)聽客戶端請求
        while (true){
            //監(jiān)聽客戶端請求
            Socket socket = serverSocket.accept();
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        String threadName = Thread.currentThread().toString();
                        //將字節(jié)流轉(zhuǎn)化成字符流,讀取客戶端輸入的內(nèi)容
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        //讀取一行數(shù)據(jù)
                        String str = bufferedReader.readLine();
                        //打印客戶端發(fā)送的信息
                        System.out.println("線程名稱" + threadName + ",服務(wù)端收到客戶端發(fā)送的信息:" + str);

                        //向客戶端返回信息,將字符轉(zhuǎn)化成字節(jié)流,并輸出
                        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                        printWriter.println("hello,我是服務(wù)端,已收到消息");

                        // 關(guān)閉流
                        bufferedReader.close();
                        printWriter.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

依次啟動服務(wù)端、客戶端,服務(wù)端控制臺輸出結(jié)果如下:

線程名稱Thread[Thread-8,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是4個,客戶端!
線程名稱Thread[Thread-4,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是8個,客戶端!
線程名稱Thread[Thread-0,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是1個,客戶端!
線程名稱Thread[Thread-7,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是5個,客戶端!
線程名稱Thread[Thread-5,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是2個,客戶端!
線程名稱Thread[Thread-9,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是3個,客戶端!
線程名稱Thread[Thread-1,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是0個,客戶端!
線程名稱Thread[Thread-3,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是7個,客戶端!
線程名稱Thread[Thread-2,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是9個,客戶端!
線程名稱Thread[Thread-6,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是6個,客戶端!

當(dāng)服務(wù)端接收到客戶端的請求時,會給每個客戶端創(chuàng)建一個新的線程進(jìn)行鏈路處理,處理完成之后,通過輸出流返回應(yīng)答給客戶端,最后線程會銷毀。

但是這樣的編程模型也有很大的弊端,如果出現(xiàn) 100、1000、甚至 10000 個客戶端同時請求服務(wù)端,采用這種編程模型,服務(wù)端也會創(chuàng)建與之相同的線程數(shù)量,線程數(shù)急劇膨脹可能會導(dǎo)致線程堆棧溢出、創(chuàng)建新線程失敗等問題,最終可能導(dǎo)致服務(wù)端宕機(jī)或者僵死,不能對外提供服務(wù)。

三、偽異步 BIO

為了解決上面提到的同步阻塞 I/O 面臨的一個鏈路需要一個線程處理的問題,后來有人對它的編程模型進(jìn)行了優(yōu)化。

在服務(wù)端通過使用 Java 中ThreadPoolExecutor線程池機(jī)制來處理多個客戶端的請求接入,防止由于海量并發(fā)接入導(dǎo)致資源耗盡,讓線程的創(chuàng)建和回收成本相對較低,保證了系統(tǒng)有限的資源得以控制,實(shí)現(xiàn)了 N (客戶端請求數(shù)量)大于 M (服務(wù)端處理客戶端請求的線程數(shù)量)的偽異步 I/O 模型。

偽異步 IO 模型圖,如下圖:

圖片圖片

采用線程池和任務(wù)隊列可以實(shí)現(xiàn)一種叫做偽異步的 I/O 通信框架,當(dāng)有新的客戶端接入時,將客戶端的 Socket 封裝成一個 Task 投遞到線程池中進(jìn)行處理。

服務(wù)端采用線程池處理客戶端請求,樣例程序如下:

public class BioServerTest {

    public static void main(String[] args) throws IOException {
        //在線程池中創(chuàng)建5個固定大小線程,來處理客戶端的請求
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        //初始化服務(wù)端socket并且綁定 8080 端口
        ServerSocket serverSocket = new ServerSocket(8080);
        //循環(huán)監(jiān)聽客戶端請求
        while (true){
            //監(jiān)聽客戶端請求
            Socket socket = serverSocket.accept();
            //使用線程池執(zhí)行任務(wù)
            executorService.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        String threadName = Thread.currentThread().toString();
                        //將字節(jié)流轉(zhuǎn)化成字符流,讀取客戶端輸入的內(nèi)容
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        //讀取一行數(shù)據(jù)
                        String str = bufferedReader.readLine();
                        //打印客戶端發(fā)送的信息
                        System.out.println("線程名稱" + threadName + ",服務(wù)端收到客戶端發(fā)送的信息:" + str);

                        //向客戶端返回信息,將字符轉(zhuǎn)化成字節(jié)流,并輸出
                        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                        printWriter.println("hello,我是服務(wù)端,已收到消息");

                        // 關(guān)閉流
                        bufferedReader.close();
                        printWriter.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

依次啟動服務(wù)端、客戶端,服務(wù)端控制臺輸出結(jié)果如下:

線程名稱Thread[pool-1-thread-4,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是6個,客戶端!
線程名稱Thread[pool-1-thread-2,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是8個,客戶端!
線程名稱Thread[pool-1-thread-3,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是9個,客戶端!
線程名稱Thread[pool-1-thread-5,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是5個,客戶端!
線程名稱Thread[pool-1-thread-1,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是7個,客戶端!
線程名稱Thread[pool-1-thread-5,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是2個,客戶端!
線程名稱Thread[pool-1-thread-5,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是0個,客戶端!
線程名稱Thread[pool-1-thread-1,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是1個,客戶端!
線程名稱Thread[pool-1-thread-5,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是3個,客戶端!
線程名稱Thread[pool-1-thread-1,5,main],服務(wù)端收到客戶端發(fā)送的信息:Hello,我是4個,客戶端!

本例中測試的客戶端數(shù)量是 10,服務(wù)端使用 java 線程池來處理任務(wù),線程數(shù)量為 5 個,服務(wù)端不用為每個客戶端都創(chuàng)建一個線程,由于線程池可以設(shè)置消息隊列的大小和最大線程數(shù),因此它的資源占用是可控的,無論多少個客戶端并發(fā)訪問,都不會導(dǎo)致資源的耗盡和宕機(jī)。

在活動連接數(shù)不是特別高的情況下,這種模型還是不錯的,可以讓每一個連接專注于自己的 I/O 并且編程模型簡單,也不用過多考慮系統(tǒng)的過載、限流等問題。

但是,它的底層仍然是同步阻塞的 BIO 模型,當(dāng)面對十萬甚至百萬級請求接入的時候,傳統(tǒng)的 BIO 模型無能為力,因此我們需要一種更高效的 I/O 處理模型來應(yīng)對更高的并發(fā)量。

四、NIO

NIO,英文全稱:Non-blocking-IO,一種同步非阻塞的 I/O 模型。

在 Java 1.4 中引入,對應(yīng)的代碼在java.nio包下。

與傳統(tǒng)的 IO 不同,NIO 新增了 Channel、Selector、Buffer 等抽象概念,支持面向緩沖、基于通道的 I/O 數(shù)據(jù)傳輸方法。

NIO 模型圖,如下圖:

圖片圖片

與此同時,NIO 還提供了與傳統(tǒng) BIO 模型中的 Socket 和 ServerSocket 相對應(yīng)的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實(shí)現(xiàn)。

NIO 這兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統(tǒng)中的 BIO 一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。

對于低負(fù)載、低并發(fā)的應(yīng)用程序,可以使用同步阻塞 I/O 來提升開發(fā)效率和更好的維護(hù)性;對于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,使用 NIO 的非阻塞模式來開發(fā)可以顯著的提升數(shù)據(jù)傳輸效率。

在介紹樣例之前,我們先看一下 NIO 涉及到的核心關(guān)聯(lián)類圖,如下:

圖片圖片

上圖中有三個關(guān)鍵類:Channel 、Selector 和 Buffer,它們是 NIO 中的核心概念。

  • Channel:可以理解為通道;
  • Selector:可以理解為選擇器;
  • Buffer:可以理解為數(shù)據(jù)緩沖區(qū);

從名詞上看感覺很抽象,我們還是用之前介紹的城市交通工具來繼續(xù)形容 NIO 的工作方式,這里的 Channel 要比 Socket 更加具體,它可以比作為某種具體的交通工具,如汽車或是高鐵、飛機(jī)等,而 Selector 可以比作為一個車站的車輛運(yùn)行調(diào)度系統(tǒng),它將負(fù)責(zé)監(jiān)控每輛車的當(dāng)前運(yùn)行狀態(tài),是已經(jīng)出站還是在路上等等,也就是說它可以輪詢每個 Channel 的狀態(tài)。

還有一個 Buffer 類,你可以將它看作為 IO 中 Stream,但是它比 IO 中的 Stream 更加具體化,我們可以將它比作為車上的座位,Channel 如果是汽車的話,那么 Buffer 就是汽車上的座位,Channel 如果是高鐵上,那么 Buffer 就是高鐵上的座位,它始終是一個具體的概念,這一點(diǎn)與 Stream 不同。

Socket 中的 Stream 只能代表是一個座位,至于是什么座位由你自己去想象,也就是說你在上車之前并不知道這個車上是否還有座位,也不知道上的是什么車,因?yàn)槟悴⒉荒苓x擇,這些信息都已經(jīng)被封裝在了運(yùn)輸工具(Socket)里面了。

NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 傳輸過程中涉及到的信息具體化,讓程序員有機(jī)會去控制它們。

當(dāng)我們進(jìn)行傳統(tǒng)的網(wǎng)絡(luò) IO 操作時,比如調(diào)用write()往 Socket 中的SendQ隊列寫數(shù)據(jù)時,當(dāng)一次寫的數(shù)據(jù)超過SendQ長度時,操作系統(tǒng)會按照SendQ 的長度進(jìn)行分割的,這個過程中需要將用戶空間數(shù)據(jù)和內(nèi)核地址空間進(jìn)行切換,而這個切換不是程序員可以控制的,由底層操作系統(tǒng)來幫我們處理。

而在Buffer中,我們可以控制Buffer的capacity(容量),并且是否擴(kuò)容以及如何擴(kuò)容都可以控制。

理解了這些概念后我們看一下,實(shí)際上它們是如何工作的呢?

我們一起來看看代碼實(shí)例!

服務(wù)端操作,樣例程序如下:

/**
 * NIO 服務(wù)端
 */
public class NioServerTest {

    public static void main(String[] args) throws IOException {
        // 打開服務(wù)器套接字通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 服務(wù)器配置為非阻塞
        ssc.configureBlocking(false);
        // 進(jìn)行服務(wù)的綁定,監(jiān)聽8080端口
        ssc.socket().bind(new InetSocketAddress(8080));

        // 構(gòu)建一個Selector選擇器,并且將channel注冊上去
        Selector selector = Selector.open();
        // 將serverSocketChannel注冊到selector,并對accept事件感興趣(serverSocketChannel只能支持accept操作)
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            // 查詢指定事件已經(jīng)就緒的通道數(shù)量,select方法有阻塞效果,直到有事件通知才會有返回,如果為0就跳過
            int readyChannels = selector.select();
            if(readyChannels == 0) {
                continue;
            };
            //通過選擇器取得所有key集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //判斷狀態(tài)是否有效
                if (!key.isValid()) {
                    continue;
                }
                if (key.isAcceptable()) {
                    // 處理通道中的連接事件
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel sc = server.accept();
                    sc.configureBlocking(false);
                    System.out.println("接收到新的客戶端連接,地址:" + sc.getRemoteAddress());

                    // 將通道注冊到選擇器并處理通道中可讀事件
                    sc.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 處理通道中的可讀事件
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    while (channel.isOpen() && channel.read(byteBuffer) != -1) {
                        // 長連接情況下,需要手動判斷數(shù)據(jù)有沒有讀取結(jié)束 (此處做一個簡單的判斷: 超過0字節(jié)就認(rèn)為請求結(jié)束了)
                        if (byteBuffer.position() > 0) {
                            break;
                        };
                    }
                    byteBuffer.flip();

                    //獲取緩沖中的數(shù)據(jù)
                    String result = new String(byteBuffer.array(), 0, byteBuffer.limit());
                    System.out.println("收到客戶端發(fā)送的信息,內(nèi)容:" + result);

                    // 將通道注冊到選擇器并處理通道中可寫事件
                    channel.register(selector, SelectionKey.OP_WRITE);
                } else if (key.isWritable()) {
                    // 處理通道中的可寫事件
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    byteBuffer.put("server send".getBytes());
                    byteBuffer.flip();
                    channel.write(byteBuffer);

                    // 將通道注冊到選擇器并處理通道中可讀事件
                    channel.register(selector, SelectionKey.OP_READ);
                    //寫完之后關(guān)閉通道
                    channel.close();
                }
                //當(dāng)前事件已經(jīng)處理完畢,可以丟棄
                iterator.remove();
            }
        }
    }
}

客戶端操作,樣例程序如下:

/**
 * NIO 客戶端
 */
public class NioClientTest {

    public static void main(String[] args) throws IOException {
        // 打開socket通道
        SocketChannel sc = SocketChannel.open();
        //設(shè)置為非阻塞
        sc.configureBlocking(false);
        //連接服務(wù)器地址和端口
        sc.connect(new InetSocketAddress("127.0.0.1", 8080));
        while (!sc.finishConnect()) {
            // 沒連接上,則一直等待
            System.out.println("客戶端正在連接中,請耐心等待");
        }

        // 發(fā)送內(nèi)容
        ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
        writeBuffer.put("Hello,我是客戶端".getBytes());
        writeBuffer.flip();
        sc.write(writeBuffer);

        // 讀取響應(yīng)
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        while (sc.isOpen() && sc.read(readBuffer) != -1) {
            // 長連接情況下,需要手動判斷數(shù)據(jù)有沒有讀取結(jié)束 (此處做一個簡單的判斷: 超過0字節(jié)就認(rèn)為請求結(jié)束了)
            if (readBuffer.position() > 0) {
                break;
            };
        }
        readBuffer.flip();

        String result = new String(readBuffer.array(), 0, readBuffer.limit());
        System.out.println("客戶端收到服務(wù)端:" + sc.socket().getRemoteSocketAddress() + ",返回的信息:" + result);

        // 關(guān)閉通道
        sc.close();
    }
}

最后,依次啟動服務(wù)端、客戶端,看看控制臺輸出情況如何。

服務(wù)端控制臺結(jié)果如下:

接收到新的客戶端連接,地址:/127.0.0.1:57644
收到客戶端發(fā)送的信息,內(nèi)容:Hello,我是客戶端

客戶端控制臺結(jié)果如下:

客戶端收到服務(wù)端:/127.0.0.1:8080,返回的信息:server send

從編程上可以看到,NIO 的操作比傳統(tǒng)的 IO 操作要復(fù)雜的多!

Selector 被稱為選擇器 ,當(dāng)然你也可以翻譯為多路復(fù)用器 。它是Java NIO 核心組件中的一個,用于檢查一個或多個 Channel(通道)的狀態(tài)是否處于連接就緒、接受就緒、可讀就緒、可寫就緒。

如此可以實(shí)現(xiàn)單線程管理多個 channels 的目的,也就是可以管理多個網(wǎng)絡(luò)連接。

使用 Selector 的好處在于 :相比傳統(tǒng)方式使用多個線程來管理 IO,Selector 使用了更少的線程就可以處理通道了,并且實(shí)現(xiàn)網(wǎng)絡(luò)高效傳輸!

雖然 Java 中的 nio 傳輸比較快,為什么大家都不愿意用 JDK 原生 NIO 進(jìn)行開發(fā)呢?

從上面的代碼中大家都可以看出來,除了編程復(fù)雜之外,還有幾個讓人詬病的問題:

  • JDK 的 NIO 底層由 epoll 實(shí)現(xiàn),該實(shí)現(xiàn)飽受詬病的空輪詢 bug 會導(dǎo)致 cpu 飆升 100%!
  • 項目龐大之后,自行實(shí)現(xiàn)的 NIO 很容易出現(xiàn)各類 bug,維護(hù)成本較高!

但是,Google 的 Netty 框架的出現(xiàn),很大程度上改善了 JDK 原生 NIO 所存在的一些讓人難以忍受的問題,關(guān)于 Netty 框架應(yīng)用,會在后期的文章里進(jìn)行介紹。

五、AIO

最后就是 AIO 了,全稱 Asynchronous I/O,可以理解為異步 IO,也被稱為 NIO 2,在 Java 7 中引入,它是異步非阻塞的 IO 模型。

異步 IO 是基于事件回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會直接返回,不會堵塞在那里,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)的線程進(jìn)行后續(xù)的操作。

具體的實(shí)例如下!

服務(wù)端操作,樣例程序如下:

/**
 * aio 服務(wù)端
 */
public class AioServer {

    public AsynchronousServerSocketChannel serverChannel;

    /**
     * 監(jiān)聽客戶端請求
     * @throws Exception
     */
    public void listen() throws Exception {
        //打開一個服務(wù)端通道
        serverChannel = AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));//監(jiān)聽8080端口
        //服務(wù)監(jiān)聽
        serverChannel.accept(this, new CompletionHandler<AsynchronousSocketChannel,AioServer>(){

            @Override
            public void completed(AsynchronousSocketChannel client, AioServer attachment) {
                try {
                    if (client.isOpen()) {
                        System.out.println("接收到新的客戶端連接,地址:" + client.getRemoteAddress());
                        final ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //讀取客戶端發(fā)送的信息
                        client.read(buffer, client, new CompletionHandler<Integer, AsynchronousSocketChannel>(){

                            @Override
                            public void completed(Integer result, AsynchronousSocketChannel attachment) {
                                try {
                                    //讀取請求,處理客戶端發(fā)送的數(shù)據(jù)
                                    buffer.flip();
                                    String content = new String(buffer.array(), 0, buffer.limit());
                                    System.out.println("服務(wù)端收到客戶端發(fā)送的信息:" + content);

                                    //向客戶端發(fā)送數(shù)據(jù)
                                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                    writeBuffer.put("server send".getBytes());
                                    writeBuffer.flip();
                                    attachment.write(writeBuffer).get();
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                                try {
                                    exc.printStackTrace();
                                    attachment.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //當(dāng)有新客戶端接入的時候,直接調(diào)用accept方法,遞歸執(zhí)行下去,保證多個客戶端都可以阻塞
                    attachment.serverChannel.accept(attachment, this);
                }
            }

            @Override
            public void failed(Throwable exc, AioServer attachment) {
                exc.printStackTrace();
            }
        });
    }

    public static void main(String[] args) throws Exception {
        //啟動服務(wù)器,并監(jiān)聽客戶端
        new AioServer().listen();
        //因?yàn)槭钱惒絀O執(zhí)行,讓主線程睡眠但不關(guān)閉
        Thread.sleep(Integer.MAX_VALUE);
    }
}

客戶端操作,樣例程序如下:

/**
 * aio 客戶端
 */
public class AioClient {

    public static void main(String[] args) throws IOException, InterruptedException {
        //打開一個客戶端通道
        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
        //與服務(wù)器建立連接
        channel.connect(new InetSocketAddress("127.0.0.1", 8080));

        //睡眠1s,等待與服務(wù)器建立連接
        Thread.sleep(1000);
        try {
            //向服務(wù)器發(fā)送數(shù)據(jù)
            channel.write(ByteBuffer.wrap("Hello,我是客戶端".getBytes())).get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            //從服務(wù)器讀取數(shù)據(jù)
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            channel.read(byteBuffer).get();//將通道中的數(shù)據(jù)寫入緩沖buffer
            byteBuffer.flip();
            String result = new String(byteBuffer.array(), 0, byteBuffer.limit());
            System.out.println("客戶端收到服務(wù)器返回的內(nèi)容:" + result);//輸出返回結(jié)果
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

同樣的,依次啟動服務(wù)端程序,再啟動客戶端程序,看看運(yùn)行結(jié)果!

服務(wù)端控制臺結(jié)果如下:

接收到新的客戶端連接,地址:/127.0.0.1:56606
服務(wù)端收到客戶端發(fā)送的信息:Hello,我是客戶端

客戶端控制臺結(jié)果如下:

客戶端收到服務(wù)器返回的內(nèi)容:server send

這種組合方式用起來十分復(fù)雜,只有在一些非常復(fù)雜的分布式情況下使用,像集群之間的消息同步機(jī)制一般用這種 I/O 組合方式。如 Cassandra 的 Gossip 通信機(jī)制就是采用異步非阻塞的方式,可以實(shí)現(xiàn)非常高的網(wǎng)絡(luò)傳輸性能。

Netty 之前也嘗試使用過 AIO,不過又放棄了!

六、小結(jié)

本文主要圍繞 BIO、NIO、AIO 等模型,結(jié)合一些樣例代碼,做了一次簡單的內(nèi)容知識總結(jié),希望對大家有所幫助。

內(nèi)容難免有所遺漏,歡迎留言指出!

七、參考

1、JDK1.7&JDK1.8 源碼

2、IBM - 許令波 -深入分析 Java I/O 的工作機(jī)制

3、Github - JavaGuide -  IO總結(jié)

4、博客園 - 五月的倉頡 -  IO和File

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2023-02-17 08:10:24

2024-04-03 15:33:04

JWTSession傳輸信息

2021-11-30 07:44:50

FinalFinallyFinalize

2024-09-19 08:42:43

2021-12-10 12:01:37

finalfinallyfinalize

2024-03-20 15:12:59

KafkaES中間件

2021-12-13 06:56:45

Comparable元素排序

2021-12-23 07:11:31

開發(fā)

2023-02-09 07:01:35

轉(zhuǎn)發(fā)重定向Java

2025-08-08 08:10:08

2020-04-16 15:20:43

PHP前端BIO

2022-04-16 16:52:24

Netty網(wǎng)絡(luò)服務(wù)器客戶端程序

2023-06-26 07:39:10

2023-02-20 07:19:14

2023-12-13 13:31:00

useEffect對象瀏覽器

2022-05-16 11:04:43

RocketMQPUSH 模式PULL 模式

2024-03-26 16:24:46

分布式事務(wù)2PC3PC

2021-12-27 06:57:40

This SuperJava

2025-03-12 08:45:15

函數(shù)聲明函數(shù)表達(dá)式IIFE

2025-08-29 07:58:42

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

精品香蕉在线观看视频一| 亚洲国产精品天堂| 国产精品自在线| 欧美偷拍第一页| 麻豆精品99| 欧美色涩在线第一页| 轻点好疼好大好爽视频| 国内精品一区视频| 国产成人免费视| 国产精品劲爆视频| 国产无精乱码一区二区三区| 欧美日韩性在线观看| 欧美大胆人体bbbb| 国产天堂在线播放| 性xxxxfjsxxxxx欧美| 国产欧美精品一区二区色综合朱莉| 91精品国产综合久久久久久丝袜 | 亚洲一区二区三区四区五区黄| 狠狠色综合欧美激情| 中文字幕一区2区3区| 亚洲大黄网站| 欧美精品制服第一页| 精品欧美一区二区久久久| 亚洲国产中文在线| 欧美日韩国产免费一区二区| 免费看国产曰批40分钟| 91精品久久久久久粉嫩| 国产片一区二区| 精品视频高清无人区区二区三区| av在线资源观看| 蜜臀久久久久久久| 日本高清+成人网在线观看| 国产亚洲精品久久久久久打不开| 久久精品影视| www.亚洲成人| 欧美成人久久久免费播放| 亚洲欧美日本伦理| 日韩精品免费在线观看| 污污免费在线观看| 亚洲精品一二三**| 91精品国产91综合久久蜜臀| 三级a三级三级三级a十八发禁止| 性欧美1819sex性高清| 午夜亚洲国产au精品一区二区| 国产免费内射又粗又爽密桃视频| 日本激情在线观看| 中文字幕制服丝袜一区二区三区 | 国产精品久久久久久久久粉嫩av| 九九热在线免费观看| 在线看片成人| 97超级碰碰碰久久久| 久久精品视频久久| 亚洲手机视频| 国内外成人免费激情在线视频| 澳门黄色一级片| 伊人情人综合网| 久久久精品在线| 国产精品久久久久久久精| 99精品在线观看| 久久久999成人| 成人观看免费视频| 欧美三级第一页| 国内精品久久久久伊人av| 国产精品999久久久| 亚洲国产精品一区制服丝袜| 68精品久久久久久欧美| 久久亚洲精品国产| 七七婷婷婷婷精品国产| 国产在线久久久| 国产黄频在线观看| www.亚洲色图| 日韩电影免费观看在| 91短视频版在线观看www免费| 国产精品毛片无遮挡高清| 一区一区视频| 国产丝袜在线观看视频| 精品免费在线视频| 中文字幕国产传媒| 国产亚洲久久| 亚洲黄色在线观看| 免费看裸体网站| 91精品福利| 91精品国产免费久久久久久| 日韩久久久久久久久久| 狠狠色丁香久久婷婷综| 黄色99视频| 在线观看免费版| 亚洲一区免费视频| 999香蕉视频| 亚洲视频国产精品| 亚洲一区av在线播放| 国产女人18水真多毛片18精品| 日韩午夜精品| 91免费观看网站| 亚洲欧美日韩免费| 中文一区一区三区高中清不卡| 最近免费观看高清韩国日本大全| 蜜桃av.网站在线观看| 欧美日韩一区国产| 人妻激情偷乱频一区二区三区| 免费国产自久久久久三四区久久| 久久精品视频在线播放| 国产精品久久久久久99| 精品在线免费观看| 久久亚裔精品欧美| a视频在线播放| 91国产视频在线观看| 亚洲精品一二三四| 国精一区二区| 午夜精品久久17c| 国产一区二区在线不卡| 久久亚洲综合色| 精品无码av无码免费专区| 粉嫩一区二区三区| 亚洲精品美女久久久久| 黄色一级大片在线免费观看| 久久久夜精品| 国产青春久久久国产毛片 | 欧美精品激情在线观看| 中文字幕一区二区久久人妻| www.在线欧美| www污在线观看| 欧美a级大片在线| 日韩中文第一页| 无码一区二区三区在线观看| 成人91在线观看| 国产传媒久久久| 日韩精品中文字幕一区二区| 色99之美女主播在线视频| 日韩综合在线观看| 91原创在线视频| 分分操这里只有精品| 亚洲精品一区二区三区在线| 久久亚洲精品成人| 亚洲天堂免费av| 亚洲国产精品国自产拍av| 久久人妻精品白浆国产| 亚洲欧洲色图| 日韩av免费一区| 日av在线播放| 欧美性猛交xxxx富婆弯腰| 艳妇乳肉亭妇荡乳av| 亚洲国产精品第一区二区| 国产欧美在线一区二区| sm久久捆绑调教精品一区| 欧美精品一区二区三区视频| 国产精品白浆一区二小说| 国产.欧美.日韩| 大西瓜av在线| 欧美天堂社区| 日本亚洲精品在线观看| 国产中文字幕在线播放| 色妞www精品视频| 国产精品久久免费观看| 毛片一区二区三区| 国产高清精品软男同| 9999精品视频| 美女视频久久黄| 国产激情视频在线播放| 亚洲影视在线播放| 亚洲观看黄色网| 美女日韩在线中文字幕| 欧美第一黄网| 精品乱码一区二区三区四区| www.久久撸.com| 精品毛片一区二区三区| 亚洲午夜激情av| 9.1成人看片| 蜜桃久久精品一区二区| 中国黄色录像片| 日韩成人午夜| 国产精品欧美日韩| av网址在线看| 日韩精品亚洲精品| 羞羞色院91蜜桃| 亚洲免费观看高清| 亚洲制服丝袜在线播放| 久久亚洲美女| 日本黄网站色大片免费观看| 林ゆな中文字幕一区二区| 日本成人免费在线| 主播国产精品| 亚洲免费成人av电影| 一炮成瘾1v1高h| 亚洲不卡av一区二区三区| 久久美女免费视频| 国产一区二区三区av电影| 人人妻人人添人人爽欧美一区| 精品国产一区一区二区三亚瑟| 亚洲自拍中文字幕| 亚洲精品中文字幕| 欧美成aaa人片免费看| 欧美老女人性开放| 91精品麻豆日日躁夜夜躁| jizz国产免费| 国产精品高清亚洲| 在线视频 日韩| 久久99国产精品久久99果冻传媒| 国产美女主播在线| 999久久久国产精品| 国内精品视频在线播放| 亚洲91在线| 国产va免费精品高清在线观看| 大地资源网3页在线观看| 亚洲精品一区中文字幕乱码| 国产欧美熟妇另类久久久| 色八戒一区二区三区| 国产一级片免费看| 亚洲视频一区二区在线观看| 白白色免费视频| 成人免费高清视频在线观看| 亚洲精品视频导航| 欧美亚洲在线| 精品久久久久久无码中文野结衣| 欧美成人自拍| 欧美日韩视频在线一区二区观看视频| 欧美区一区二区| 国产综合色香蕉精品| 成人勉费视频| 97视频在线免费观看| 在线中文字幕视频观看| 日韩中文字在线| 国产人成在线观看| 日韩理论片久久| 秋霞av鲁丝片一区二区| 日韩午夜精品视频| 91亚洲欧美激情| 欧美性感一类影片在线播放| 国产婷婷色一区二区在线观看| 亚洲电影在线免费观看| 99视频只有精品| 亚洲视频你懂的| 婷婷久久综合网| 亚洲天堂网中文字| 永久免费看mv网站入口| 国产精品美日韩| 国产传媒在线看| 欧美国产成人精品| 日本一卡二卡在线播放| 国产日韩欧美精品电影三级在线| 一卡二卡三卡四卡| 久久久影视传媒| 白白色免费视频| 国产欧美日本一区二区三区| 九九热免费在线| 国产精品每日更新在线播放网址 | 香蕉视频色在线观看| 激情深爱一区二区| 久久久久久久高清| 国产精品一品二品| 亚洲最大视频网| 成人福利视频网站| 亚洲激情 欧美| 99国产精品久| 波多野结衣av在线观看| 国产日本一区二区| 国精产品视频一二二区| 中文字幕一区二| 欧美日韩精品在线观看视频| 亚洲一区二区三区三| 天天操天天爽天天干| 色综合色狠狠综合色| 国产情侣呻吟对白高潮| 欧美日韩一二三| 91女人18毛片水多国产| 精品国产一区二区三区四区四| 你懂的网站在线| 亚洲欧美第一页| 思思99re6国产在线播放| 欧美成人免费网| 电影在线观看一区| 国产精品久久久久久网站| 996久久国产精品线观看| 国产91视觉| 国产精品入口久久| 国产精品无码乱伦| 亚洲人体大胆视频| 日本熟妇人妻中出| 国产精品中文字幕欧美| 久久精品老司机| 1000精品久久久久久久久| 久久精品欧美一区二区| 91高清视频在线| 国产欧美熟妇另类久久久 | 手机电影在线观看| 欧美中文在线免费| 国产精品一区二区美女视频免费看 | 日本精品免费视频| 一区二区三区四区五区在线| 成年人在线观看视频免费| 丁香婷婷综合激情五月色| 婷婷色一区二区三区 | 人人爽人人爽人人片av| 欧美精品aⅴ在线视频| 手机在线精品视频| xxx一区二区| 天堂av中文在线观看| 亚洲va欧美va在线观看| 国产成人精品三级高清久久91| av 日韩 人妻 黑人 综合 无码| 久久激情视频| 亚洲av无码成人精品区| 亚洲国产精品99久久久久久久久 | 欧美丝袜丝交足nylons图片| 亚洲精品一区二区三区四区| 中文字幕亚洲欧美| 中文字幕在线高清| 动漫美女被爆操久久久| 91精品国产91久久综合| 中文字幕无码不卡免费视频| 国产精品69久久久久水密桃| 免费视频91蜜桃| 富二代精品短视频| 精品人妻少妇嫩草av无码专区| 一本色道久久88精品综合| 不卡av免费观看| av资源一区二区| 97精品中文字幕| 中文字幕av不卡在线| 91看片淫黄大片一级| 国产精品日日夜夜| 日韩欧美一区二区视频| 黄av在线播放| 国产拍精品一二三| av资源久久| 毛片av免费在线观看| 99精品欧美一区二区三区小说| 久久久久成人精品无码| 欧美一区二区三区在线观看视频| 在线观看麻豆| 国产精品一区二区三区在线播放 | 欧美国产日韩a欧美在线观看 | 超碰在线网站| 亚洲专区国产精品| 欧美影视一区| 丰满少妇一区二区三区专区| 亚洲日穴在线视频| 国产精品-色哟哟| 久久五月天综合| 国产区一区二| 777久久精品一区二区三区无码 | 午夜影视一区二区三区| a级国产乱理论片在线观看99| 一区二区免费不卡在线| 日本网站在线看| 亚洲一区在线视频观看| 亚洲成人精品女人久久久| 欧美激情手机在线视频 | 日韩高清中文字幕| 午夜av不卡| 日韩欧美亚洲v片| 久久丁香综合五月国产三级网站| 人人艹在线视频| 91精品国产综合久久蜜臀| gogogogo高清视频在线| 成人在线观看91| av成人黄色| 欧美丰满老妇熟乱xxxxyyy| 欧美日韩一二三区| 色婷婷在线播放| 精品国产一区二区三区四区精华 | 色婷婷av久久久久久久| 福利在线播放| 亚洲一区二区三区毛片| 亚洲欧洲午夜| 变态另类ts人妖一区二区| 91精品国产免费久久综合| 乱插在线www| 欧美另类一区| 麻豆一区二区三| 69精品久久久| 在线精品播放av| 久久gogo国模啪啪裸体| 少妇人妻无码专区视频| 久久九九全国免费| 99久久精品国产成人一区二区| 欧美精品久久久久久久免费观看 | 隔壁人妻偷人bd中字| 91蝌蚪porny| av男人天堂av| 国产激情综合五月久久| 亚洲精品二区三区| 少妇光屁股影院| 欧美一区二区视频网站| 久草在线资源福利站| 亚洲国产一区二区精品视频 | 日韩av中文字幕一区二区| 777777国产7777777| 亚洲精品视频免费在线观看| 欧美综合社区国产| 无码人妻丰满熟妇区96| 中文字幕五月欧美| 日韩有码电影| 51国偷自产一区二区三区的来源| 亚洲欧美日韩在线观看a三区| 国产破处视频在线观看| 亚洲激情成人网| 久久伊人影院| 91插插插插插插插插| 亚洲成年人网站在线观看|