在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

BIO、NIO、AIO 模型工作方式

科技綠洲 ? 來(lái)源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-09-30 10:43 ? 次閱讀

一、簡(jiǎn)介

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

在講解 BIO、NIO、AIO 之前,我們先來(lái)回顧一下這幾個(gè)概念: 同步與異步,阻塞與非阻塞

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

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

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

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

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

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

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

二、BIO

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

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

BIO 通信模型圖如下!

圖片

一般在服務(wù)端通過(guò)while(true)循環(huán)中會(huì)調(diào)用accept()方法監(jiān)聽(tīng)客戶(hù)端的連接,一旦接收到一個(gè)連接請(qǐng)求,就可以建立通信套接字進(jìn)行讀寫(xiě)操作,此時(shí)不能再接收其他客戶(hù)端連接請(qǐng)求,只能等待同當(dāng)前連接的客戶(hù)端的操作執(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)聽(tīng)客戶(hù)端請(qǐng)求
        while (true){
            try {
                //監(jiān)聽(tīng)客戶(hù)端請(qǐng)求
                Socket socket = serverSocket.accept();

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

                //向客戶(hù)端返回信息,將字符轉(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();
            }
        }
    }
}

客戶(hù)端操作,樣例程序如下

public class BioClientTest {

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

                @Override
                public void run() {
                    try {
                        //通過(guò)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 + "個(gè),客戶(hù)端!";
                        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("客戶(hù)端發(fā)送請(qǐng)求內(nèi)容:" + str + " - > 收到服務(wù)端返回的內(nèi)容:" + result);

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

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

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

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

客戶(hù)端控制臺(tái)結(jié)果如下:

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

隨著客戶(hù)端的請(qǐng)求次數(shù)越來(lái)越多,可能需要排隊(duì)的時(shí)間會(huì)越來(lái)越長(zhǎng),因此是否可以在服務(wù)端,采用多線(xiàn)程編程進(jìn)行處理呢?

答案是,可以的!

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

public class BioServerTest {

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

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

                        //向客戶(hù)端返回信息,將字符轉(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();
        }
    }
}

依次啟動(dòng)服務(wù)端、客戶(hù)端,服務(wù)端控制臺(tái)輸出結(jié)果如下:

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

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

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

三、偽異步 BIO

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

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

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

圖片

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

服務(wù)端采用線(xiàn)程池處理客戶(hù)端請(qǐng)求,樣例程序如下:

public class BioServerTest {

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

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

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

                        //向客戶(hù)端返回信息,將字符轉(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();
                    }
                }
            });
        }
    }
}

依次啟動(dòng)服務(wù)端、客戶(hù)端,服務(wù)端控制臺(tái)輸出結(jié)果如下:

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

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

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

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

四、NIO

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

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

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

NIO 模型圖,如下圖:

圖片

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

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

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

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

圖片

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        while (true){
            // 查詢(xún)指定事件已經(jīng)就緒的通道數(shù)量,select方法有阻塞效果,直到有事件通知才會(huì)有返回,如果為0就跳過(guò)
            int readyChannels = selector.select();
            if(readyChannels == 0) {
                continue;
            };
            //通過(guò)選擇器取得所有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("接收到新的客戶(hù)端連接,地址:" + sc.getRemoteAddress());

                    // 將通道注冊(cè)到選擇器并處理通道中可讀事件
                    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) {
                        // 長(zhǎng)連接情況下,需要手動(dòng)判斷數(shù)據(jù)有沒(méi)有讀取結(jié)束 (此處做一個(gè)簡(jiǎn)單的判斷: 超過(guò)0字節(jié)就認(rèn)為請(qǐng)求結(jié)束了)
                        if (byteBuffer.position() > 0) {
                            break;
                        };
                    }
                    byteBuffer.flip();

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

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

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

客戶(hù)端操作,樣例程序如下

/**
 * NIO 客戶(hù)端
 */
public class NioClientTest {

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

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

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

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

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

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

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

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

客戶(hù)端控制臺(tái)結(jié)果如下:

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

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

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

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

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

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

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

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

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

五、AIO

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

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

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

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

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

    public AsynchronousServerSocketChannel serverChannel;

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

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

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

                                    //向客戶(hù)端發(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)有新客戶(hù)端接入的時(shí)候,直接調(diào)用accept方法,遞歸執(zhí)行下去,保證多個(gè)客戶(hù)端都可以阻塞
                    attachment.serverChannel.accept(attachment, this);
                }
            }

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

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

客戶(hù)端操作,樣例程序如下

/**
 * aio 客戶(hù)端
 */
public class AioClient {

    public static void main(String[] args) throws IOException, InterruptedException {
        //打開(kāi)一個(gè)客戶(hù)端通道
        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,我是客戶(hù)端".getBytes())).get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            //從服務(wù)器讀取數(shù)據(jù)
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            channel.read(byteBuffer).get();//將通道中的數(shù)據(jù)寫(xiě)入緩沖buffer
            byteBuffer.flip();
            String result = new String(byteBuffer.array(), 0, byteBuffer.limit());
            System.out.println("客戶(hù)端收到服務(wù)器返回的內(nèi)容:" + result);//輸出返回結(jié)果
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

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

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

客戶(hù)端控制臺(tái)結(jié)果如下:

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

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

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

六、小結(jié)

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

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 網(wǎng)絡(luò)數(shù)據(jù)傳輸

    關(guān)注

    0

    文章

    4

    瀏覽量

    6681
  • 模型
    +關(guān)注

    關(guān)注

    1

    文章

    3342

    瀏覽量

    49271
  • BIO
    BIO
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

    9388
  • 非阻塞
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    2196
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    GPIO基本結(jié)構(gòu)和工作方式介紹

    GPIO的8種工作方式一、GPIO基本結(jié)構(gòu)和工作方式1、戰(zhàn)艦/精英板2、Min板3、基本結(jié)構(gòu)4、工作方式二、GPIO寄存器說(shuō)明1、GPIO相關(guān)寄存器2、端口配置低寄存器(GPIOx_CRL)、端口
    發(fā)表于 01-11 07:02

    SPI總線(xiàn)的工作方式是什么?

    SPI總線(xiàn)具有哪些特點(diǎn)?SPI總線(xiàn)的工作方式是什么?
    發(fā)表于 01-25 06:57

    α調(diào)制工作方式原理

    以單相—單相直接變頻電路為例說(shuō)明α調(diào)制工作方式的原理及其實(shí)現(xiàn)方法。圖4.2為單相橋式AC/AC變換電路。為了在負(fù)載一獲得交變電壓,可以交替地讓正組變流器和負(fù)組變流器輪流
    發(fā)表于 07-27 09:10 ?572次閱讀
    α調(diào)制<b class='flag-5'>工作方式</b>原理

    MAX714工作方式及其轉(zhuǎn)換

    工作方式及其轉(zhuǎn)換 MAX714系列
    發(fā)表于 10-27 14:14 ?652次閱讀
    MAX714<b class='flag-5'>工作方式</b>及其轉(zhuǎn)換

    鼠標(biāo)的工作方式

    鼠標(biāo)的工作方式 工作方式是指鼠標(biāo)采用什么工作原理或方式進(jìn)行工作。常見(jiàn)的鼠標(biāo)工作方式有滾輪式和光
    發(fā)表于 12-28 11:38 ?838次閱讀

    Wifi模塊的工作方式功能是什么?

    Wifi模塊的工作方式是什么呢,Wifi模塊的主要功能又有哪些呢?本文主要介紹了有關(guān)Wifi模塊的基礎(chǔ)知識(shí)即:Wifi模塊的工作方式、主要功能及應(yīng)用領(lǐng)域。
    發(fā)表于 06-12 14:22 ?5921次閱讀

    步進(jìn)電機(jī)及驅(qū)動(dòng)電路工作原理及工作方式介紹

    步進(jìn)電機(jī)及驅(qū)動(dòng)電路工作原理及工作方式介紹
    發(fā)表于 05-11 18:00 ?0次下載

    tomcat 線(xiàn)程池介紹 BIO/NIO有何不同

    有著重要的影響。這篇文章將從Connector入手,討論一些與Connector有關(guān)的重要問(wèn)題,包括NIO/BIO模式、線(xiàn)程池、連接數(shù)等。
    發(fā)表于 05-01 09:44 ?6656次閱讀

    ups不間斷電源工作方式

    本視頻主要詳細(xì)介紹了ups不間斷電源工作方式,分別是正常運(yùn)行方式、電池工作方式、旁路運(yùn)行方式以及旁路維護(hù)方式
    的頭像 發(fā)表于 06-26 14:38 ?7526次閱讀

    AD級(jí)聯(lián)的工作方式配置和AD雙排序的工作方式配置詳細(xì)說(shuō)明

    本文檔的主要內(nèi)容詳細(xì)介紹的是AD級(jí)聯(lián)的工作方式配置和AD雙排序的工作方式配置詳細(xì)說(shuō)明
    發(fā)表于 12-23 08:00 ?2次下載
    AD級(jí)聯(lián)的<b class='flag-5'>工作方式</b>配置和AD雙排序的<b class='flag-5'>工作方式</b>配置詳細(xì)說(shuō)明

    UPS電源有哪些工作方式

    UPS電源是較為常見(jiàn)的應(yīng)急電源系統(tǒng),其在市電正常與市電異常的情況下,工作方式也有所不同,以下介紹UPS電源的四種工作方式:正常運(yùn)行、電池工作、旁路運(yùn)行和旁路維護(hù)。1、正常運(yùn)行方式
    發(fā)表于 11-09 09:06 ?31次下載
    UPS電源有哪些<b class='flag-5'>工作方式</b>?

    Java AIO又稱(chēng)為NIO 2.0,難道它也是基于NIO來(lái)實(shí)現(xiàn)的?

    2011年Java 7發(fā)布,里面增加了AIO稱(chēng)之為異步IO的編程模型,但已經(jīng)過(guò)去了近12年,平時(shí)使用的開(kāi)發(fā)框架中間件,還是以NIO為主,例如網(wǎng)絡(luò)框架Netty、Mina,Web容器Tomcat、Undertow。
    的頭像 發(fā)表于 03-23 09:26 ?1509次閱讀

    最常見(jiàn)的直流負(fù)載工作方式

    最常見(jiàn)的直流負(fù)載工作方式? 直流負(fù)載工作方式是指在直流電路中使用的各種負(fù)載方式。直流負(fù)載是用于測(cè)試和測(cè)量直流電源輸出能力和能效的設(shè)備,可以模擬真實(shí)負(fù)載條件下的電流和功率需求。本文將詳細(xì)介紹最常
    的頭像 發(fā)表于 01-18 15:12 ?872次閱讀

    DC電源模塊的原理及工作方式

    BOSHIDA ?DC電源模塊的原理及工作方式 DC電源模塊是一種將交流電轉(zhuǎn)換為直流電的設(shè)備,它將交流電輸入端轉(zhuǎn)換為穩(wěn)定的直流電輸出,以供電子設(shè)備使用。DC電源模塊的工作原理及工作方式如下。 ?DC
    的頭像 發(fā)表于 04-24 10:37 ?972次閱讀
    DC電源模塊的原理及<b class='flag-5'>工作方式</b>

    斬波器的定頻調(diào)寬工作方式

    斬波器的定頻調(diào)寬工作方式是一種電力電子控制技術(shù),用于調(diào)節(jié)輸出電壓或電流。
    的頭像 發(fā)表于 05-24 16:12 ?764次閱讀
    主站蜘蛛池模板: 手机在线完整视频免费观看 | 亚洲国产美女精品久久 | 天天久久影视色香综合网 | 色爱综合区| 夜夜爽夜夜爱 | 国产高清免费 | 五月婷婷丁香在线 | 久久99精品久久久久久久野外 | 国产精品久久国产三级国不卡顿 | 成人夜色香网站在线观看 | 欧美熟色妇 | 亚洲综合五月天 | 久久久久毛片成人精品 | 最新亚洲一区二区三区四区 | 亚洲视频在线不卡 | 毛片录像| 久操资源在线 | 色在线视频观看 | 国产欧美日韩视频免费61794 | 色多多拼多多网站 | xxxx大片| 日本aaaa| 奇米影视大全 | 大又大又粗又爽女人毛片 | 四虎在线永久视频观看 | 最新天堂网 | 色视频2 | 午夜视频福利在线 | 国产手机在线 | 一级做a爰片久久毛片美女图片 | 国产成人精品日本亚洲语言 | 国产精品天天干 | 成人看片在线观看 | 奇米久久久 | 国产精品黄网站免费观看 | 欧美极品第一页 | 在线免费视频你懂的 | 狠狠做深爱婷婷久久一区 | 尤物蜜芽福利国产污在线观看 | 成年人啪啪网站 | 香蕉视频在线免费播放 |