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

          從I/O多路復(fù)用到Netty,還要跨過Java NIO包

          共 5891字,需瀏覽 12分鐘

           ·

          2021-04-11 03:34

          d6bda640333b53c09ddc085372c289ac.webp

          本文是Netty系列第4篇

          上一篇文章我們深入了解了I/O多路復(fù)用的三種實(shí)現(xiàn)形式,select/poll/epoll。

          那Netty是使用哪種實(shí)現(xiàn)的I/O多路復(fù)用呢?這個(gè)問題,得從Java NIO包說起。

          Netty實(shí)際上也是一個(gè)封裝好的框架,它的網(wǎng)絡(luò)I/O本質(zhì)上還是使用了Java的NIO包(New IO,不是網(wǎng)絡(luò)I/O模型的NIO,Nonblocking IO)包。所以,從網(wǎng)絡(luò)I/O模型到Netty,我們還需要了解下Java NIO包。

          本文預(yù)計(jì)閱讀時(shí)間 5 分鐘,將重點(diǎn)回答以下幾個(gè)問題

          • 如何用Java NIO包實(shí)現(xiàn)一個(gè)服務(wù)端

          • Java NIO包如何實(shí)現(xiàn)I/O多路復(fù)用模型

          • 有了Java NIO包,為什么還要封裝一個(gè)Netty?

          1.先來看一個(gè)Java NIO服務(wù)端的例子

          上一篇文章我們已經(jīng)了解了I/O多路復(fù)用的實(shí)現(xiàn)形式。
          就是多個(gè)的進(jìn)程的IO可以注冊(cè)到一個(gè)復(fù)用器(selector)上,然后用一個(gè)進(jìn)程調(diào)用select,select會(huì)監(jiān)聽所有注冊(cè)進(jìn)來的IO。

          NIO包做了對(duì)應(yīng)的實(shí)現(xiàn)。如下圖所示。

          fd42e1e62116f79361601a401210ff8c.webp


          有一個(gè)統(tǒng)一的selector負(fù)責(zé)監(jiān)聽所有的Channel。這些channel中只要有一個(gè)有IO動(dòng)作,就可以通過Selector.select()方法檢測(cè)到,并且使用selectedKeys得到這些有IO的channel,然后對(duì)它們調(diào)用相應(yīng)的IO操作。

          我們來個(gè)簡(jiǎn)單的demo做一下演示。如何使用NIO中三個(gè)核心組件(Buffer緩沖區(qū)、Channel通道、Selector選擇器)來編寫一個(gè)服務(wù)端程序。

          public class NioDemo {
          public static void main(String[] args) {
          try {
          //1.創(chuàng)建channel
          ServerSocketChannel socketChannel1 = ServerSocketChannel.open();
          //設(shè)置為非阻塞模式,默認(rèn)是阻塞的
          socketChannel1.configureBlocking(false);
          socketChannel1.socket().bind(new InetSocketAddress("127.0.0.1", 8811));

          ServerSocketChannel socketChannel2 = ServerSocketChannel.open();
          socketChannel2.configureBlocking(false);
          socketChannel2.socket().bind(new InetSocketAddress("127.0.0.1", 8822));

          //2.創(chuàng)建selector,并將channel1和channel2進(jìn)行注冊(cè)。
          Selector selector = Selector.open();
          socketChannel1.register(selector, SelectionKey.OP_ACCEPT);
          socketChannel2.register(selector, SelectionKey.OP_ACCEPT);

          while (true) {
          //3.一直阻塞直到有至少有一個(gè)通道準(zhǔn)備就緒
          int readChannelCount = selector.select();
          Set<SelectionKey> selectionKeys = selector.selectedKeys();
          Iterator<SelectionKey> iterator = selectionKeys.iterator();
          //4.輪訓(xùn)已經(jīng)就緒的通道
          while (iterator.hasNext()) {
          SelectionKey key = iterator.next();
          iterator.remove();
          //5.判斷準(zhǔn)備就緒的事件類型,并作相應(yīng)處理
          if (key.isAcceptable()) {
          // 創(chuàng)建新的連接,并且把連接注冊(cè)到selector上,并且聲明這個(gè)channel只對(duì)讀操作感興趣。
          ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
          SocketChannel socketChannel = serverSocketChannel.accept();
          socketChannel.configureBlocking(false);
          socketChannel.register(selector, SelectionKey.OP_READ);
          }
          if (key.isReadable()) {
          SocketChannel socketChannel = (SocketChannel) key.channel();
          ByteBuffer readBuff = ByteBuffer.allocate(1024);
          socketChannel.read(readBuff);
          readBuff.flip();
          System.out.println("received : " + new String(readBuff.array()));
          socketChannel.close();
          }
          }
          }
          } catch (IOException e) {
          e.printStackTrace();
          }
          }
          }

          通過這個(gè)代碼示例,我們能清楚地了解如何用Java NIO包實(shí)現(xiàn)一個(gè)服務(wù)端:

          • 1)創(chuàng)建channel1和channel2,分別監(jiān)聽特定端口。

          • 2)創(chuàng)建selector,并將channel1和channel2進(jìn)行注冊(cè)。

          • 3)selector.select()一直阻塞,直到有至少有一個(gè)通道準(zhǔn)備就緒。

          • 4)輪訓(xùn)已經(jīng)就緒的通道

          • 5)并根據(jù)事件類型做出相應(yīng)的響應(yīng)動(dòng)作。

          程序啟動(dòng)后,會(huì)一直阻塞在selector.select()。
          通過瀏覽器調(diào)用localhost:8811 或者 localhost:8822就能觸發(fā)我們的服務(wù)端代碼了。

          2.Java NIO包如何實(shí)現(xiàn)I/O多路復(fù)用模型

          上文演示的Java NIO服務(wù)端已經(jīng)比較清楚地展示了使用NIO編寫服務(wù)端程序的過程。

          那這個(gè)過程中如何實(shí)現(xiàn)了I/O多路復(fù)用的呢?

          我們得深入看下selector的實(shí)現(xiàn)。

          //2.創(chuàng)建selector,并將channel1和channel2進(jìn)行注冊(cè)。
          Selector selector = Selector.open();

          從open這里開始吧。

          bc30ebeceb5ae2a287b4d429d77b7daa.webp

          這里用了一個(gè)SelectorProvider來創(chuàng)建selector。

          進(jìn)入SelectorProvider.provider(),看到具體的provider是由
          sun.nio.ch.DefaultSelectorProvider創(chuàng)建的,對(duì)應(yīng)的方法是:

          24bf4349e8aa159a322731ec7fb6bf8b.webp


          咦?原來不同的操作系統(tǒng)會(huì)提供不同的provider對(duì)象。這里包括了PollSelectorProvider、EPollSelectorProvide等。

          名字是不是有點(diǎn)眼熟?

          沒錯(cuò),跟我們上一篇文章分析過的I/O多路復(fù)用的不同實(shí)現(xiàn)方式poll/epoll有關(guān)。

          我們選擇默認(rèn)的
          sun.nio.ch.PollSelectorProvider往下看看。

          aabaec1853bf5ff1d07db0e01161b6a8.webp

          OK,找到了實(shí)現(xiàn)類PollSelectorImpl。

          然后,通過以下調(diào)用:

          a060ca5a5e7146ad95ebe68114497983.webp


          找到最終的native方法poll0。

          a8ad80d98ef3f31d0215811748e557a7.webp

          是不是仍然很眼熟?

          沒錯(cuò)!跟我們上一篇文章分析過的poll函數(shù)是一致的。

          int poll (struct pollfd *fds, unsigned int nfds, int timeout);

          繞了這么久,到最后,還是找到了我們聊過I/O多路復(fù)用的 poll 實(shí)現(xiàn)。

          至此,我們終于把Java NIO和 I/O多路復(fù)用模型串聯(lián)起來了。

          Java NIO包使用selector,實(shí)現(xiàn)了I/O多路復(fù)用模型。

          同時(shí),在不同的操作系統(tǒng)中,會(huì)有不同的poll/epoll選擇。

          3.為什么還需要Netty呢?

          那既然已經(jīng)有了NIO包了,我們可以自己手動(dòng)編寫服務(wù)框架了,為什么還需要封裝一個(gè)Netty框架呢?有什么好處呢?

          好處當(dāng)然是有很多了!我們從一開始實(shí)現(xiàn)的demo說起。

          3.1 設(shè)計(jì)模式的優(yōu)化

          我們的demo確實(shí)已經(jīng)能夠工作了,但是還是有比較明顯的問題。第4步(輪詢已經(jīng)就緒的通道)和第5步(對(duì)事件作相應(yīng)處理)是在同一個(gè)線程中的,當(dāng)事件處理比較耗時(shí)甚至阻塞時(shí),整個(gè)流程就會(huì)阻塞了。

          我們使用的實(shí)際上就是 “單Reactor單線程” 設(shè)計(jì)模式。

          3c2d04641602ba05bd245dd0c683cb7d.webp


          這種模型在Reactor中負(fù)責(zé)監(jiān)聽端口、接收請(qǐng)求,如果是連接事件交給acceptor處理,如果是讀寫事件和業(yè)務(wù)處理就交給handler處理,但始終只有一個(gè)線程執(zhí)行所有的事情。

          為了提高性能,我們理所當(dāng)然相當(dāng)可以把事件處理交給線程池,那就可以演進(jìn)為 “單Reactor多線程” 設(shè)計(jì)模式。

          60ebf5986d0202e38ad1d85f822a881c.webp


          這種模型和第一種模型的主要區(qū)別是把業(yè)務(wù)處理從之前的單一線程脫離出來,換成線程池處理。Reactor線程只處理連接事件、讀寫事件,所有業(yè)務(wù)處理都交給線程池,充分利用多核機(jī)器的資源,提高性能。

          但是這仍然不夠!

          我們可以發(fā)現(xiàn),一個(gè)Reactor線程承擔(dān)了所有的網(wǎng)絡(luò)事件,例如監(jiān)聽和響應(yīng),高并發(fā)場(chǎng)景下單線程存在性能問題。

          為了充分利用多核能力,可以構(gòu)建兩個(gè) Reactor,主 Reactor 單獨(dú)監(jiān)聽server socket,accept新連接,然后將建立的 SocketChannel 注冊(cè)給指定的從 Reactor,從Reactor再執(zhí)行事件的讀寫、分發(fā),把業(yè)務(wù)處理就扔給worker線程池完成。這就演進(jìn)為 ”主從Reactor模式“ 設(shè)計(jì)模式。

          a01a48ae9787aee8c72d821d6a842c98.webp


          所以,如果有人直接幫我們 封裝好這樣的設(shè)計(jì)模式 ,是不是太好了?

          沒錯(cuò),Netty就是這樣的“活雷鋒”!
          Netty就使用了主從Reactor模式封裝了Java NIO包的使用,大大提高了性能。

          3.2 其他優(yōu)點(diǎn) (以后的核心知識(shí)點(diǎn))

          除了封裝了高性能的設(shè)計(jì)模式外,Netty還有許多其他優(yōu)點(diǎn):

          • 穩(wěn)定性。 Netty 更加可靠穩(wěn)定,修復(fù)和完善了 JDK NIO 較多已知問題,包括 select 空轉(zhuǎn)導(dǎo)致 CPU 消耗 100%、keep-alive 檢測(cè)等問題。

          • 性能優(yōu)化。對(duì)象池復(fù)用技術(shù)。Netty 通過復(fù)用對(duì)象,避免頻繁創(chuàng)建和銷毀帶來的開銷。零拷貝技術(shù)。 除了操作系統(tǒng)級(jí)別的零拷貝技術(shù)外,Netty 提供了面向用戶態(tài)的零拷貝技術(shù),在 I/O 讀寫時(shí)直接使用 DirectBuffer,避免了數(shù)據(jù)在堆內(nèi)存和堆外內(nèi)存之間的拷貝。

          • 便捷性。 Netty 提供了很多常用的工具,例如行解碼器、長(zhǎng)度域解碼器等。如果我們使用JDK NIO包,那么這些常用工具都需要自己進(jìn)行實(shí)現(xiàn)。

          正是因?yàn)?Netty 做到了高性能、高穩(wěn)定性、高易用性,完美彌補(bǔ)了 Java NIO 的不足,所以在我們?cè)诰W(wǎng)絡(luò)編程時(shí),首選Netty,而不是自己直接使用Java NIO。


          回顧一下前幾章內(nèi)容,到目前為止,我們從網(wǎng)絡(luò)I/O模型出發(fā),一步步了解到了Netty的網(wǎng)絡(luò)I/O模型。

          對(duì)于I/O多路復(fù)用、Java NIO包 和 Netty 的關(guān)系也有了全面的認(rèn)識(shí)。

          有了這些知識(shí)基礎(chǔ),我們初步了解了Netty是什么,為什么使用Netty。

          后面的文章,我們將逐步展開Netty框架的核心知識(shí)點(diǎn),敬請(qǐng)期待。




          往期熱門筆記合集推薦:


          原創(chuàng):阿丸筆記(微信公眾號(hào):aone_note),歡迎 分享,轉(zhuǎn)載請(qǐng)保留出處。

          掃描下方二維碼可以關(guān)注我哦~

                                                                                        覺得不錯(cuò),就點(diǎn)個(gè) 再看 吧??


          如果有任何疑問或者建議,歡迎 寫留言 或者 微信 和我聯(lián)系哦~

          瀏覽 48
          點(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>
                  淫色无码| 成人偷拍小视频 | 亚洲网在线播放 | 激情五月成人网 | 色婷婷亚洲精品天天 |