<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          初識(shí)NIO

          共 12752字,需瀏覽 26分鐘

           ·

          2023-06-20 15:37

          有誰(shuí)跟我一樣,本以為沒了疫情之后,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,翻譯過(guò)來(lái)就是阻塞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、客戶端通過(guò)telnet連接服務(wù)端 0034c8d2b66c94bee14fe63661038034.webp

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

          5c866e148039e8e4cced4bf8b58a361b.webp1.3、通過(guò)客戶端發(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ù),接下來(lái)通過(guò)telnet發(fā)送數(shù)據(jù),方法是按下ctrl+]

          b5bbfdcd0aa0a0a11e37bf81eae973a7.webp

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

          5ec24c5e3eb7363e8346ab2133634e1f.webp

          接下來(lái)通過(guò)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的演示過(guò)程,可以看到,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)重的問(wèn)題,假如說(shuō)第一個(gè)請(qǐng)求因?yàn)槟承┰蜻t遲未處理,那后面的請(qǐng)求也都會(huì)被阻塞,那既然它是單線程,我們可以不可以通過(guò)多線程來(lái)處理,一個(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();
                
                
                          }
                
                
                      }
                
              

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

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

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

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

          2、存在阻塞問(wèn)題

          3、線程切換造成的開銷

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

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

          那有沒有辦法解決這種問(wèn)題,肯定是有的,其實(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ù)打印出來(lái) 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、通過(guò)客戶端連接

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

          209a3825027225b07fba805b02c32fe7.webp909e8ba78796fbe02976de7ffc582450.webp

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

          2.3、通過(guò)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ù)打印出來(lái) 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ù)端來(lái)說(shuō)是一個(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ù)端的角度來(lái)看就是read,它要read客戶端發(fā)來(lái)的數(shù)據(jù) 4、客戶端向服務(wù)端發(fā)送數(shù)據(jù),觸發(fā)read監(jiān)聽,通過(guò)SocketChannel將數(shù)據(jù)讀取出來(lái)
          注:selectionKey其實(shí)就是對(duì)應(yīng)的ServerSocketChannel或者SocketChannel,通過(guò)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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜芽精品。con | 午夜电影无码 | 无码一二三 | 豆花视频网站入口18 | 日韩综合网站 |