1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        初識(shí)NIO

        共 12752字,需瀏覽 26分鐘

         ·

        2023-06-20 15:37

        有誰跟我一樣,本以為沒了疫情之后,IT行業(yè)會(huì)回暖,但事實(shí)卻是今年更卷了,跳槽非常難,在這種背景下,要么你技術(shù)很牛逼,要么你學(xué)歷很吊,但巧了,筆者正好是渣本加菜雞,為了能被晚淘汰兩年,現(xiàn)在下決心多學(xué)點(diǎn)東西。 從現(xiàn)在開始,我準(zhǔn)備學(xué)習(xí)NIO相關(guān)知識(shí),因?yàn)槲疫@塊是一片空白,如果你也不會(huì),那就一起學(xué),如果你會(huì),那你教教我好不好?

        一、BIO

        什么是BIO?其實(shí)就是初學(xué)java的時(shí)候老師講的I/O,比如InputStream、OutputStream等很多stream,它們都在java.io包下:

        152931d93e9b099b8c38bec806d4a74a.webp

        BIO的全稱叫Blocking IO,翻譯過來就是阻塞IO,為了讓你們有個(gè)直觀的認(rèn)識(shí),先看段程序:

              
                package com.example.io;
              
              
                
                  
        import java.io.IOException; import java.net.ServerSocket; import java.net.Socket;
        public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000); ????????while?(true)?{ System.out.println("等待連接..."); Socket socket = serverSocket.accept(); System.out.println("有客戶端連接了..."); handle(socket); } }
        private static void handle(Socket socket) throws IOException { byte[] bytes = new byte[1024]; System.out.println("準(zhǔn)備read..."); ????????int?read?=?socket.getInputStream().read(bytes); System.out.println("read完畢..."); ????????if?(read?!=?-1)?{ System.out.println("接收到客戶端的數(shù)據(jù): " + new String(bytes, 0, read)); } socket.getOutputStream().flush(); } }

        這段程序就是java基礎(chǔ)中的網(wǎng)絡(luò)編程,它其實(shí)就是基于BIO開發(fā)的。

        1.1、啟動(dòng)服務(wù)端

        在第15行打個(gè)斷點(diǎn),運(yùn)行程序

        0bbca8a3748e98f26d4f5d58a271716a.webp

        但你會(huì)發(fā)現(xiàn)程序并沒有運(yùn)行到第15行,當(dāng)然肯定也不會(huì)卡在第12行,那就是卡在第13行了,BIO的B就是這個(gè)意思,它會(huì)阻塞住。

        1.2、客戶端通過telnet連接服務(wù)端 0034c8d2b66c94bee14fe63661038034.webp

        回車后你會(huì)發(fā)現(xiàn),程序走了

        5c866e148039e8e4cced4bf8b58a361b.webp1.3、通過客戶端發(fā)送數(shù)據(jù)

        上面的調(diào)試到了handle方法,意思就是要處理這個(gè)請(qǐng)求,在23行打個(gè)斷點(diǎn),放開請(qǐng)求

        223e2a44499c7c10071be00aab6de409.webp

        你會(huì)發(fā)現(xiàn)又卡住了,很明顯這次卡在了第22行,這行代碼的意思是在等待客戶端發(fā)送數(shù)據(jù),因?yàn)閯偛诺膖elnet只是連接上了客戶端,并沒有發(fā)送數(shù)據(jù),接下來通過telnet發(fā)送數(shù)據(jù),方法是按下ctrl+]

        b5bbfdcd0aa0a0a11e37bf81eae973a7.webp

        如果對(duì)telnet不熟悉的話,可以通過help命令查看一下:

        5ec24c5e3eb7363e8346ab2133634e1f.webp

        接下來通過send發(fā)送一條數(shù)據(jù):send xiaoP

        db7a1363fe2591c6fd87b0ce469d72ec.webp

        回車后你會(huì)發(fā)現(xiàn)程序走了

        34d5827a90772ab61b83489d4447edff.webp

        斷點(diǎn)放開后,服務(wù)端成功收到了客戶端發(fā)送的內(nèi)容:

        01a0806e9beaac9f555e14b632129f52.webp

        以上就是簡(jiǎn)單的BIO的演示過程,可以看到,accept和read方法都是阻塞的,其實(shí)不僅阻塞,也是同步的,把斷點(diǎn)都去掉,然后運(yùn)行程序

        f3fc393a6d70cbe1b67995e5ba78d81b.webp

        然后打開兩個(gè)客戶端

        df72ad8ad4cce1ae506b1b26c0b88fcf.webp

        然后在左邊的窗口按ctrl+],再在右邊的窗口按ctrl+]

        594fb4ae138742bc3d156f96a2831d58.webp

        然后在右邊的窗口先send數(shù)據(jù)

        12ef4f20021cdabd4961bcae50236882.webp

        可以看到,我右邊已經(jīng)回車發(fā)送了,但是服務(wù)端并沒有打印xiaoP2,

        7338201645eb2ed51ab662556a82e5f2.webp

        這時(shí)候我在左邊的窗口回車

        ee7634a4391b6c480ecd47aa9ad3cfbb.webp

        這時(shí)候控制臺(tái)打印了

        45188d50ebda2b5c9878bd64ef427181.webp

        可以看到,服務(wù)端在處理請(qǐng)求的時(shí)候是串行的,也就是單線程,必須處理完第一個(gè)請(qǐng)求才會(huì)處理第二個(gè),這樣的話就會(huì)有一個(gè)嚴(yán)重的問題,假如說第一個(gè)請(qǐng)求因?yàn)槟承┰蜻t遲未處理,那后面的請(qǐng)求也都會(huì)被阻塞,那既然它是單線程,我們可以不可以通過多線程來處理,一個(gè)線程處理一個(gè)請(qǐng)求,這樣就不會(huì)被阻塞了?稍微改下程序

              
                public static void main(String[] args) throws IOException {
              
              
                        ServerSocket serverSocket = new ServerSocket(9000);
              
              
                        while (true) {
              
              
                            System.out.println("等待連接...");
              
              
                            Socket socket = serverSocket.accept();
              
              
                            System.out.println("有客戶端連接了...");
              
              
                            new Thread(new Runnable() {
              
              
                                @Override
              
              
                                public void run() {
              
              
                                    try {
              
              
                                        handle(socket);
              
              
                                    } catch (IOException e) {
              
              
                                        e.printStackTrace();
              
              
                                    }
              
              
                                }
              
              
                            }).start();
              
              
                        }
              
              
                    }
              
            

        重新跑起來,然后重復(fù)上面的步驟,你就會(huì)發(fā)現(xiàn)xiaoP2打印出來了。

        但是這樣還是會(huì)有問題,這樣的程序處理幾個(gè)請(qǐng)求可以,實(shí)際開發(fā)中,如果一次來幾十萬甚至上百萬個(gè)請(qǐng)求,你能開幾十萬個(gè)線程嗎?這種cpu不存在吧?那你可能會(huì)說,用線程池呢?那假如你線程池開了300個(gè)線程,一次處理300個(gè)請(qǐng)求,等你處理完幾十萬個(gè)請(qǐng)求,客戶端那邊早超時(shí)了,這還不是最致命的,上面演示了,如果一個(gè)客戶端連接了服務(wù)端但是不急著發(fā)送數(shù)據(jù),它會(huì)一直占用著線程資源不釋放。

        總結(jié)下BIO的缺點(diǎn):

        1、需要開大量的線程處理請(qǐng)求

        2、存在阻塞問題

        3、線程切換造成的開銷

        4、連接但不發(fā)送數(shù)據(jù),會(huì)占用線程不釋放

        所以BIO的使用場(chǎng)景是很少的,除非你的項(xiàng)目需要的連接數(shù)很少。

        那有沒有辦法解決這種問題,肯定是有的,其實(shí)在jdk1.4之后,引入了nio,其中n表示none blocking,非阻塞。 e1a02b39ba689ec0210f07ef4b6fca42.webp二、初識(shí)NIO
        2.1、最簡(jiǎn)單的NIO程序

        我們先用nio的API寫一個(gè)簡(jiǎn)單的程序并啟動(dòng)它

              
                package com.example.nio;
              
              
                
                  
        import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List;
        public class NioServer {
        /** * 存放SocketChannel */ private static List<SocketChannel> list = new ArrayList<>();
        public static void main(String[] args) throws IOException { //nio中叫ServerSocketChannel,bio中叫ServerSocket ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //綁定到9000端口 serverSocketChannel.socket().bind(new InetSocketAddress(9000)); //設(shè)置為非阻塞 serverSocketChannel.configureBlocking(false); System.out.println("服務(wù)啟動(dòng)成功!"); while (true) { //如果上面的configureBlocking設(shè)置為true,那這行代碼和bio中的accept是一樣的,都是阻塞的 SocketChannel socketChannel = serverSocketChannel.accept(); if (socketChannel != null) { System.out.println("連接成功!"); //設(shè)置為非阻塞 socketChannel.configureBlocking(false); list.add(socketChannel); } //遍歷連接進(jìn)行數(shù)據(jù)讀取 Iterator<SocketChannel> iterator = list.iterator(); while (iterator.hasNext()) { SocketChannel next = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = next.read(byteBuffer); //如果有數(shù)據(jù),把數(shù)據(jù)打印出來 if(len > 0) { System.out.println("接收到數(shù)據(jù):" + new String(byteBuffer.array())); } else if (len == -1) { //如果客戶端斷開,把socket從集合中移除 iterator.remove(); System.out.println("客戶端斷開連接"); } } } } }
        513e2754f8e2039ab4f37045aa4b027c.webp2.2、通過客戶端連接

        還是和bio中的調(diào)試一樣,開兩個(gè)客戶端,先連接第一個(gè)客戶端,但是先在第二個(gè)客戶端中發(fā)送數(shù)據(jù)

        209a3825027225b07fba805b02c32fe7.webp909e8ba78796fbe02976de7ffc582450.webp

        可以看到,沒有bio那種阻塞的問題了,不管你開再多的連接,都可以處理。為什么?最本質(zhì)的區(qū)別就是accept和read方法不再是阻塞的了,這樣的話主線程其實(shí)一直在做循環(huán),不斷的在找客戶端連接,不斷的在讀數(shù)據(jù)(如果客戶端發(fā)送有的話)。你是不是覺得這樣的程序就沒問題了?怎么可能,試想一下,假如有1萬個(gè)連接,但是只有一個(gè)連接發(fā)數(shù)據(jù)了,那為了讀取這一個(gè)連接的數(shù)據(jù),每次都要循環(huán)一遍這一萬個(gè)連接?合適嗎?那如何優(yōu)化??jī)?yōu)化的目的很明確,就是我們只需要找到有數(shù)據(jù)的那個(gè)連接就行了,具體怎么做?nio這么強(qiáng)大,它肯定已經(jīng)做好了,這個(gè)東西叫多路復(fù)用器,也就是大名鼎鼎的selector。

        2.3、通過selector改良程序
              
                package com.example.nio;
              
              
                
                  
        import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set;
        public class NioSelectorServer {
        public static void main(String[] args) throws IOException { //nio中叫ServerSocketChannel,bio中叫ServerSocket ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //綁定到9000端口 serverSocketChannel.socket().bind(new InetSocketAddress(9000)); //設(shè)置為非阻塞 serverSocketChannel.configureBlocking(false);
        /** * 打開selector處理channel,即創(chuàng)建epoll */ Selector selector = Selector.open(); //將serverSocketChannel注冊(cè)到selector上,并且selector對(duì)客戶端的accept連接操作感興趣 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服務(wù)啟動(dòng)成功!");
        while (true) { //阻塞等待需要處理的事件發(fā)生,這里是阻塞的,有事件的話才會(huì)往下執(zhí)行 selector.select(); //獲取selector中注冊(cè)的全部事件的SelectionKey實(shí)例 Set selectionKeys = selector.selectedKeys(); Iterator iterator = selectionKeys.iterator(); //遍歷selectionKey對(duì)事件進(jìn)行處理 while (iterator.hasNext()) { SelectionKey next = iterator.next(); //如果是OP_ACCEPT事件,則進(jìn)行連接獲取和事件注冊(cè) if (next.isAcceptable()) { ServerSocketChannel channel = (ServerSocketChannel) next.channel(); SocketChannel socketChannel = channel.accept(); socketChannel.configureBlocking(false); //這里只注冊(cè)了讀事件,如果需要給客戶端發(fā)送數(shù)據(jù),可以注冊(cè)寫事件 socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客戶端連接成功!"); } else if (next.isReadable()) { //如果是OP_READ事件,則進(jìn)行讀取和打印 SocketChannel channel = (SocketChannel) next.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = channel.read(byteBuffer); //如果有數(shù)據(jù),把數(shù)據(jù)打印出來 if (len > 0) { System.out.println("接收到數(shù)據(jù):" + new String(byteBuffer.array())); } else if(len == -1) { //如果客戶端斷開連接,關(guān)閉Socket System.out.println("客戶端斷開連接"); channel.close(); } } //從事件集合里刪除本次處理的key,防止下次selector重復(fù)處理 iterator.remove(); } } } }

        啟動(dòng)程序,觀察

        b10c764dc848a67c8ac72d71b9eae2d3.webp

        可以看到,程序阻塞在了第31行,selector解決了程序一直空跑的情況,這時(shí)候打開一個(gè)客戶端,運(yùn)行telnet localhost 9000,會(huì)發(fā)現(xiàn)程序往下執(zhí)行了,telnet相當(dāng)于一個(gè)accept事件,往下debug會(huì)進(jìn)入next.isAcceptable判斷中,放開斷點(diǎn),你會(huì)發(fā)現(xiàn)程序又阻塞在31行,這時(shí)候發(fā)送數(shù)據(jù)

        29c80c199c2bb2b70c9b52f3f2b7e7a4.webpdf9cb10c89a87b9c09a63d5a5e31ed17.webp

        程序又往下走了

        3696b75d97f8860c59f465304503cdb9.webp

        放開斷點(diǎn),這時(shí)候會(huì)進(jìn)入到next.isReadable中,因?yàn)榭蛻舳私o服務(wù)端發(fā)數(shù)據(jù),對(duì)服務(wù)端來說是一個(gè)讀事件

        d6114b8eb2780d284e4c96029c10cd8b.webp

        放開斷點(diǎn),讓程序運(yùn)行

        ea6c9d0141f5ef9220dd9f3248a41dbe.webp

        打印了數(shù)據(jù),并且程序繼續(xù)阻塞在selector.select這行代碼。這是不是就實(shí)現(xiàn)了我們想要的功能,有數(shù)據(jù)的時(shí)候才會(huì)處理,那原理是什么?看一張圖

        878fd270b7c41946902862afe6c0fc0a.webp

        對(duì)照著代碼看這一張圖

        1、服務(wù)端啟動(dòng)的時(shí)候,注冊(cè)到selector,監(jiān)聽accept事件,返回selectionKey 2、客戶端連接服務(wù)端的時(shí)候,會(huì)觸發(fā)客戶端的accept監(jiān)聽,這時(shí)候很關(guān)鍵啊,服務(wù)端也就是ServerSocketChannel調(diào)用accept方法后會(huì)返回一個(gè)SocketChannel,這玩意和客戶端是一一對(duì)應(yīng)的 3、然后將SocketChannel注冊(cè)到selector,監(jiān)聽read事件,同樣也會(huì)返回selectionKey,客戶端向服務(wù)端發(fā)數(shù)據(jù),站在服務(wù)端的角度來看就是read,它要read客戶端發(fā)來的數(shù)據(jù) 4、客戶端向服務(wù)端發(fā)送數(shù)據(jù),觸發(fā)read監(jiān)聽,通過SocketChannel將數(shù)據(jù)讀取出來
        注:selectionKey其實(shí)就是對(duì)應(yīng)的ServerSocketChannel或者SocketChannel,通過selectionKey可以拿到ServerSocketChannel或SocketChannel 以上就是nio運(yùn)行的大概流程。
        那底層是怎么實(shí)現(xiàn)的?下篇文章見!
        瀏覽 26
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久精品动漫 | 日亚欧美中文免费看 | 高清无码一区 | 国产偷窥片 | 动漫美女狂揉下部羞羞 | 日本在线视频一区二区三区 | 日本啊啊视频 | 99久久久免费视频 | 一级片老女人 | 日日操网 |