<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>

          Netty學習基礎:BIO、NIO、AIO

          共 8720字,需瀏覽 18分鐘

           ·

          2022-04-17 00:21


          其實我的重點呢,是來和大家一起學習接下來的Netty篇


          然而嘞,這個Netty又不太合適直接講,為啥呢,我們學習一門技術必須知道這門技術的由來的初衷是啥,對吧


          ? ? ? ? ? ? ??


          先來給大家簡單的介紹一下Netty是什么


          Netty是一個提供異步事件驅動的網(wǎng)絡應用程序框架,用以快速開發(fā)高性能、高可靠的網(wǎng)絡服務器和客戶端程序


          Netty簡化了網(wǎng)絡程序的開發(fā),屬于BIO、NIO、AIO的演變中的產物,屬于一種NIO框架


          在我們平時使用的很多中間件中,很多底層通信都是采用的Netty,比如rocketmq、dubbo,這些我們最常見的底層通信都是用的netty,足以可見這個的性能是多么的優(yōu)秀了


          ok,接下來再來理解一下同步、異步、阻塞、非阻塞這四個概念

          ?

          從簡單的開始,我們以經(jīng)典的讀取文件的模型舉例。(對操作系統(tǒng)而言,所有的輸入輸出設備都被抽象成文件。)

          ?

          在發(fā)起讀取文件的請求時,應用層會調用系統(tǒng)內核的I/O接口。


          阻塞和非阻塞

          ?

          如果應用層調用的是阻塞型I/O,那么在調用之后,應用層即刻被掛起,一處于等待數(shù)據(jù)返回的狀態(tài),直到系統(tǒng)內核從磁盤讀取完數(shù)據(jù)并返回給應用層,應用層才用獲得的數(shù)據(jù)進行接下來的其他操作。

          ?

          如果應用層調用的是非阻塞I/O,那么調用后,系統(tǒng)內核會立即返回(雖然還沒有文件內容的數(shù)據(jù)),應用層并不會被掛起,它可以做其他任意它想做的操作。(至于文件內容數(shù)據(jù)如何返回給應用層,這已經(jīng)超出了阻塞和非阻塞的辨別范疇。)

          ?

          這便是(脫離同步和異步來說之后)阻塞和非阻塞的區(qū)別。總結來說,是否是阻塞還是非阻塞,關注的是接口調用(發(fā)出請求)后等待數(shù)據(jù)返回時的狀態(tài)。被掛起無法執(zhí)行其他操作的則是阻塞型的,可以被立即「抽離」去完成其他「任務」的則是非阻塞型的。

          同步和異步


          阻塞和非阻塞解決了應用層等待數(shù)據(jù)返回時的狀態(tài)問題,那系統(tǒng)內核獲取到的數(shù)據(jù)到底如何返回給應用層呢?這里不同類型的操作便體現(xiàn)的是同步和異步的區(qū)別。

          ?

          對于同步型的調用,應用層需要自己去向系統(tǒng)內核問詢,如果數(shù)據(jù)還未讀取完畢,那此時讀取文件的任務還未完成,應用層根據(jù)其阻塞和非阻塞的劃分,或掛起或去做其他事情(所以同步和異步并不決定其等待數(shù)據(jù)返回時的狀態(tài));如果數(shù)據(jù)已經(jīng)讀取完畢,那此時系統(tǒng)內核將數(shù)據(jù)返回給應用層,應用層即可以用取得的數(shù)據(jù)做其他相關的事情。

          ?

          而對于異步型的調用,應用層無需主動向系統(tǒng)內核問詢,在系統(tǒng)內核讀取完文件數(shù)據(jù)之后,會主動通知應用層數(shù)據(jù)已經(jīng)讀取完畢,此時應用層即可以接收系統(tǒng)內核返回過來的數(shù)據(jù),再做其他事情。

          ?

          這便是(脫離阻塞和非阻塞來說之后)同步和異步的區(qū)別。也就是說,是否是同步還是異步,關注的是任務完成時消息通知的方式。由調用方盲目主動問詢的方式是同步調用,由被調用方主動通知調用方任務已完成的方式是異步調用。

          上面這幾個概念大家一定要搞懂,這是基礎,必須好好理解上面這些,才能真正理解netty的出處,這也是面試常被問到的點之一

          ?

          總結一下

          ?

          阻塞和非阻塞,關注的是發(fā)起請求之后等待數(shù)據(jù)返回時的狀態(tài),被掛起無法執(zhí)行其他操作的是阻塞型的,可以立即去進行其他作業(yè)的是非阻塞型的

          ?

          同步和異步,關注的是任務完成時的消息通知的方式,由調用方主動去詢問的方式屬于同步調用,而被調用方主動通知調用方該任務已完成的方式屬于異步調用

          ?

          這個在網(wǎng)上最常見的一個例子就是燒水的例子了,我也繼續(xù)給大家啰嗦一下咯

          ?

          老王燒水,老王把水放在爐子上,在這里干等著,啥也沒有去做,并且需要隨時看著水是否開了,這叫阻塞同步,阻塞是因為老王啥也不能去做,同步是因為水開他得自己看著

          ?

          老王后來學精了,不在這里傻等著了,把水放在爐子上之后,然后就去開了一把緊張又刺激的lol手游,這叫非阻塞同步,非阻塞是因為老王在等水期間自己打游戲了,同步是因為水開他還是得自己看著

          ?

          后來,老王覺得自己看著水太麻煩了,于是買了個升級版的水壺,牛了啊,這個水壺把水煮開了之后,會吹哨,哎

          ?

          老王不需要每隔幾分鐘就去看一眼水是否開了,只需要聽這個哨聲即可,做水期間可以打游戲,并且水開了還會主動通知老王,這就是異步非阻塞,非阻塞就是因為老王可以去玩游戲,異步就是水壺的那個哨子

          ?

          這下大家應該很好理解了吧!


          ? ? ? ? ? ? ??


          接下來繼續(xù)看BIO、NIO、AIO



          Socket 網(wǎng)絡通信過程簡單來說分為下面 4 步:


          建立服務端并且監(jiān)聽客戶端請求


          客戶端請求,服務端和客戶端建立連接


          兩端之間可以傳遞數(shù)據(jù)


          關閉資源


          傳統(tǒng)的阻塞式通信BIO流程


          BIO就是屬于最傳統(tǒng)的一種阻塞同步的通信方式,也是屬于最簡單的一種,使用起來比較方便,但是處理并發(fā)能力低,通信比較耗時


          服務器會通過一個線程負責監(jiān)聽客戶端請求和為每一個客戶端創(chuàng)建一個新的線程進行鏈路的處理,屬于一種典型的請求應答模式,若客戶端數(shù)量增加,則需要頻繁的創(chuàng)建和銷毀線程,會給服務器增加很大的壓力


          服務器提供IP地址和監(jiān)聽的端口,客戶端通過TCP的三次握手和服務器建立連接通信,連接成功之后,雙方進行通過,之后通過四次揮手進行斷開連接


          即使用線程池的方式來改進新增加線程,這也是屬于一種偽異步IO,這樣實現(xiàn)能夠為少數(shù)的客戶端提供服務,如果客戶端并發(fā)量足夠多,還是會因為線程池滿導致OOM的問題



          給大家看一個簡單的Demon

          public?class?SocketServer?{
          public static void main(String[] args) throws IOException { SocketServer socketServer = new SocketServer(); socketServer.start(9000); }
          public void start(int port) { //1.創(chuàng)建 ServerSocket 對象并且綁定一個端口 try (ServerSocket server = new ServerSocket(port);) { System.out.println("server start"); Socket socket; //2.通過 accept()方法監(jiān)聽客戶端請求, 這個方法會一直阻塞到有一個連接建立 while ((socket = server.accept()) != null) { System.out.println("client connected"); try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
          ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) { //3.通過輸入流讀取客戶端發(fā)送的請求信息 String message = (String) objectInputStream.readObject(); System.out.println("server receive message:" + message);
          //4.通過輸出流向客戶端發(fā)送響應信息 objectOutputStream.writeObject(message); objectOutputStream.flush();
          } catch (IOException | ClassNotFoundException e) { System.out.println("occur exception:"); } } } catch (IOException e) { System.out.println("occur IOException:"); } }
          }


          這是服務端的代碼

          public?class?Client?{
          public Object send(String message, String host, int port) {
          //1. 創(chuàng)建Socket對象并且指定服務器的地址和端口號 try (Socket socket = new Socket(host, port)) {
          ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
          //2.通過輸出流向服務器端發(fā)送請求信息 objectOutputStream.writeObject(message);
          //3.通過輸入流獲取服務器響應的信息 ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); return objectInputStream.readObject(); } catch (ClassNotFoundException | IOException e) { System.out.println("occur exception:"); } return null; }
          public static void main(String[] args) { Client helloClient = new Client(); helloClient.send("content from client", "127.0.0.1", 9000); System.out.println("發(fā)送數(shù)據(jù)成功"); }}


          這是客戶端的代碼,我們接下來先運行服務器,再運行客戶端,看效果



          服務器啟動之后,便會一直阻塞在這里,等待客戶端的連接處理



          接著我們啟動客戶端,然后看到發(fā)送數(shù)據(jù)成功,此時我們再切換到服務器的控制臺,看下效果



          我們也可以通過命令行直接執(zhí)行telnet?localhost 9000去連接服務端,效果如下



          從上面例子看出的問題


          我們看到服務器和客戶端成功的進行通信了,也就是這段服務器的代碼只能同時為一個客戶端服務,當然有改進方法,我們監(jiān)聽到連接之后,就立刻new Thread().start()創(chuàng)建一個線程用于這個客戶端接下來的處理



          這也就意味著,每一個客戶端都要建立一個線程為其處理,如果客戶端數(shù)量很多,或者說客戶端處理很慢,那就很糟糕了


          我們從線程文章中也介紹過線程是一個很寶貴的資源,我們需要合理的利用這些資源,需要根據(jù)機器的性能去合理的控制線程的數(shù)量


          即使線程池可以優(yōu)化上面的例子,讓線程創(chuàng)建和銷毀的成本降低,我們也可以執(zhí)行線程池的最大數(shù)量,控制線程資源的使用,但是,即使如何改進,我們并沒有從根本上解決這個問題,根本上還是屬于BIO,也就是同步阻塞IO的模式


          NIO

          ?

          同步非阻塞模型,在JDK1.4中引入了NIO的框架,NIO 中的 N 可以理解為 Non-blockingNIO是面向緩沖Buffer的,基于通道Channel的操作

          ?

          NIO提供了和傳統(tǒng)BIO模型中的ServerSocketSocket相對應的ServerSocketChannelSocketChannel兩種不同的套接字通道,對應服務端和客戶端

          ?

          兩種通道都支持阻塞和非阻塞的模式

          ?

          阻塞模式一般不會被使用,既然使用了阻塞,那就意味著使用起來就像上面的BIO一樣了,性能和可靠性都不是很好

          ?

          非阻塞模式,對于高負載和高并發(fā)的網(wǎng)絡應用是很友好的,后續(xù)我們要說的Netty就是基于這個改進的

          ?

          NIO 相對于BIO來說一大進步。客戶端和服務器之間通過Channel通信。NIO可以在Channel進行讀寫操作。這些Channel都會被注冊在Selector多路復用器上。Selector通過一個線程不停的輪詢這些Channel。找出已經(jīng)準備就緒的Channel執(zhí)行IO操作。

          ?

          NIO 通過一個線程輪詢,實現(xiàn)千萬個客戶端的請求,這就是非阻塞NIO的特點。


          ? ? ? ? ?

          ?

          NIO核心組件

          ?

          Channel:和流不同,通道是雙向的。NIO可以通過Channel進行數(shù)據(jù)的讀,寫和同時讀寫操作。通道分為兩大類:一類是網(wǎng)絡讀寫(SelectableChannel),一類是用于文件操作(FileChannel),我們使用的SocketChannelServerSocketChannel都是SelectableChannel的子類

          ?

          Buffer:它是NIOBIO的一個重要區(qū)別。BIO是將數(shù)據(jù)直接寫入或讀取到Stream對象中。而NIO的數(shù)據(jù)操作都是在緩沖區(qū)中進行的。緩沖區(qū)實際上是一個數(shù)組

          ?

          Selector和Selection Key:多路復用器提供選擇已經(jīng)就緒的任務的能力。就是Selector會不斷地輪詢注冊在其上的通道(Channel),如果某個通道處于就緒狀態(tài),會被Selector輪詢出來,然后通過SelectionKey可以取得就緒的Channel集合,從而進行后續(xù)的IO操作。服務器端只要提供一個線程負責Selector的輪詢,就可以接入成千上萬個客戶端

          ?

          接下來我們看使用的例子


          public class NioServer {
          static List channelList = new ArrayList<>();
          public static void main(String[] args) throws IOException {

          ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9000));
          //設置serverSocketChannel為非阻塞 serverSocketChannel.configureBlocking(false); System.out.println("服務器啟動成功");
          while (true){ //非阻塞模式的accept不會阻塞,否則會阻塞 //NIO的非阻塞是由操作系統(tǒng)實現(xiàn)的,底層調用了Linux內核的accept函數(shù) SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ //此時有客戶端連接 System.out.println("有客戶端連接"); socketChannel.configureBlocking(false); channelList.add(socketChannel); }
          //遍歷 Iterator iterator = channelList.iterator(); while (iterator.hasNext()){ SocketChannel channel = iterator.next();
          ByteBuffer byteBuffer = ByteBuffer.allocate(128); int read = channel.read(byteBuffer); if(read > 0){ System.out.println("接收到消息:" + new String(byteBuffer.array())); }else if(read == -1){ iterator.remove(); System.out.println("客戶端斷開連接");????????????????}????????????}????????} }}


          這里我們只寫了服務端的代碼,客戶端就通過telnet來模擬就行了


          我們用debug的模式看下服務端



          啟動成功之后,發(fā)現(xiàn)NIO模式下竟然沒有在accept函數(shù)這里阻塞,而是直接執(zhí)行過去了


          NIO優(yōu)點

          ?

          NIO最大的優(yōu)點,就是引入了IO多路復用機制,使得一個服務器可以同時為大量的客戶端提供服務的同時,效率也不會低,而這個IO多路復用這里,經(jīng)常遇到的一個面試題就是selectpollepoll的區(qū)別,這個我會單獨開一篇給大家說清楚,這一篇放不下了

          ?

          NIO存在的問題

          ?

          NIO跨平臺和兼容性問題

          ?

          使用NIO的時候需要考慮Linux平臺和Windows平臺的兼容性問題,如果該程序運行在多個平臺,則需要考慮測試多個平臺

          ?

          NIO2看起來很理想,但是NIO2只支持Jdk1.7+,若你的程序在Java1.6上運行,則無法使用NIO2。另外,Java7NIO2中沒有提供DatagramSocket的支持,所以NIO2只支持TCP程序,不支持UDP程序

          ?

          NIO對緩沖區(qū)的聚合和分散操作可能會導致內存泄露

          ?

          很多Channel的實現(xiàn)支持GatherScatter。這個功能允許從從多個ByteBuffer中讀入或寫入,這樣做可以有更好的性能。

          ?

          例如,你可能希望header在一個ByteBuffer中,而body在另外的ByteBuffer中;

          ?

          下圖顯示的是Scatter(分散),將ScatteringByteBuffer中的數(shù)據(jù)分散讀取到多個ByteBuffer中:


          下圖顯示的是Gather(聚合),將多個ByteBuffer的數(shù)據(jù)寫入到GatheringByteChannel



          可惜Gather/Scatter功能會導致內存泄露,知道Java7才解決內存泄露問題。使用這個功能必須小心編碼和Java版本

          ?

          Squashing the famous epoll bug(壓碎著名的epoll bug)

          ?

          著名的epoll-bug也可能會導致無效的狀態(tài)選擇和100%CPU利用率。要解決epoll-bug的唯一方法是回收舊的選擇器,將先前注冊的通道實例轉移到新創(chuàng)建的選擇器上。

          ?

          不是十分的清楚這里,感興趣的可以去更深的了解下這里

          ?

          還有一個很真實貼切的問題,就是這個對于開發(fā)者來說太不友好了,開發(fā)成本和維護成本都比較高


          ? ? ? ? ? ??


          AIO

          ?

          AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。

          ?

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

          ?

          AIO 是異步IO的縮寫,雖然 NIO 在網(wǎng)絡操作中,提供了非阻塞的方法,但是 NIO IO 行為還是同步的。對于 NIO 來說,我們的業(yè)務線程是在 IO 操作準備好時,得到通知,接著就由這個線程自行進行 IO 操作,IO操作本身是同步的。

          ?

          AIO 并沒有采用NIO的多路復用器,而是使用異步通道的概念。其readwrite方法的返回類型都是Future對象


          Future模型是異步的,其核心思想是:去主函數(shù)等待時間。AIO模型中通過AsynchronousSocketChannelAsynchronousServerSocketChannel完成套接字通道的實現(xiàn)。非阻塞,異步

          ?

          結束語


          感謝大家能夠做我最初的讀者和傳播者,請大家相信,只要你給我一份愛,我終究會還你們一頁情的。


          我會持續(xù)更新技術文章,和生活中的暴躁文章,歡迎大家關注【左耳君】,我們一起乘千里風、破萬里浪


          哦對了,后續(xù)所有的文章都會更新到這里


          https://github.com/DayuMM2021/Java






          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美性猛交XXXX乱大交 | 婷婷五月天激情丁香 | 加勒比很很操免费视频 | 伊人91在线 | 美女高潮视频 |