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

          3萬(wàn)字加50張圖,帶你深度解析 Netty 架構(gòu)與原理(上)

          共 9925字,需瀏覽 20分鐘

           ·

          2021-01-21 01:58

          走過(guò)路過(guò)不要錯(cuò)過(guò)

          點(diǎn)擊藍(lán)字關(guān)注我們


          接下來(lái)我們會(huì)學(xué)習(xí)一個(gè) Netty 系列教程,Netty 系列由「架構(gòu)與原理」,「源碼」,「架構(gòu)」三部分組成,今天我們先來(lái)看看第一部分:Netty 架構(gòu)與原理初探,大綱如下:

          • 前言

          • 1. Netty 基礎(chǔ)

            • 1.4.1. 緩沖區(qū)(Buffer)

            • 1.4.2. 通道(Channel)

            • 1.4.3. 選擇器(Selector)

            • 1.1. Netty 是什么

            • 1.2. Netty 的應(yīng)用場(chǎng)景

            • 1.3. Java 中的網(wǎng)絡(luò) IO 模型

            • 1.4. Java NIO API 簡(jiǎn)單回顧

            • 1.5. 零拷貝技術(shù)

          • 2. Netty 的架構(gòu)與原理

            • 2.2.1. 單 Reactor 單線程模式

            • 2.2.2. 單 Reactor 多線程模式

            • 2.2.3. 主從 Reactor 多線程模式

            • 2.1. 為什么要制造 Netty

            • 2.2. 幾種 Reactor 線程模式

            • 2.3. Netty 的模樣

            • 2.4. 基于 Netty 的 TCP Server/Client 案例

            • 2.5. Netty 的 Handler 組件

            • 2.6. Netty 的 Pipeline 組件

            • 2.7. Netty 的 EventLoopGroup 組件

            • 2.8. Netty 的 TaskQueue

            • 2.9. Netty 的 Future 和 Promise

          • 3. 結(jié)束語(yǔ)

          前言

          讀者在閱讀本文前最好有 Java 的 IO 編程經(jīng)驗(yàn)(知道 Java 的各種 IO 流),以及 Java 網(wǎng)絡(luò)編程經(jīng)驗(yàn)(用 ServerSocket 和 Socket 寫過(guò) demo),并對(duì) Java NIO 有基本的認(rèn)識(shí)(至少知道 Channel、Buffer、Selector 中的核心屬性和方法,以及三者如何配合使用的),以及 JUC 編程經(jīng)驗(yàn)(至少知道其中的 Future 異步處理機(jī)制),沒(méi)有也沒(méi)關(guān)系,文中多數(shù)會(huì)介紹,不影響整體的理解。

          文中對(duì)于 Reactor 的講解使用了幾張來(lái)自網(wǎng)絡(luò)上的深灰色背景的示意圖,但未找到原始出處,文中已標(biāo)注“圖片來(lái)源于網(wǎng)絡(luò)”。

          Netty 的設(shè)計(jì)復(fù)雜,接口和類體系龐大,因此我會(huì)從不同的層次對(duì)有些 Netty 中的重要組件反復(fù)描述,以幫助讀者理解。

          1. Netty 基礎(chǔ)

          基礎(chǔ)好的同學(xué),如果已經(jīng)掌握了 Java NIO 并對(duì) IO 多路復(fù)用的概念有一定的認(rèn)知,可以跳過(guò)本章。

          1.1. Netty 是什么

          1)Netty 是 JBoss 開(kāi)源項(xiàng)目,是異步的、基于事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,它以高性能、高并發(fā)著稱。所謂基于事件驅(qū)動(dòng),說(shuō)得簡(jiǎn)單點(diǎn)就是 Netty 會(huì)根據(jù)客戶端事件(連接、讀、寫等)做出響應(yīng),關(guān)于這點(diǎn),隨著文章的論述的展開(kāi),讀者自然會(huì)明白。

          2)Netty 主要用于開(kāi)發(fā)基于 TCP 協(xié)議的網(wǎng)絡(luò) IO 程序(TCP/IP 是網(wǎng)絡(luò)通信的基石,當(dāng)然也是 Netty 的基石,Netty 并沒(méi)有去改變這些底層的網(wǎng)絡(luò)基礎(chǔ)設(shè)施,而是在這之上提供更高層的網(wǎng)絡(luò)基礎(chǔ)設(shè)施),例如高性能服務(wù)器段/客戶端、P2P 程序等。

          3)Netty 是基于 Java NIO 構(gòu)建出來(lái)的,Java NIO 又是基于 Linux 提供的高性能 IO 接口/系統(tǒng)調(diào)用構(gòu)建出來(lái)的。關(guān)于 Netty 在網(wǎng)絡(luò)中的地位,下圖可以很好地表達(dá)出來(lái):


          1.2. Netty 的應(yīng)用場(chǎng)景

          在互聯(lián)網(wǎng)領(lǐng)域,Netty 作為異步高并發(fā)的網(wǎng)絡(luò)組件,常常用于構(gòu)建高性能 RPC 框架,以提升分布式服務(wù)群之間調(diào)用或者數(shù)據(jù)傳輸?shù)牟l(fā)度和速度。例如 Dubbo 的網(wǎng)絡(luò)層就可以(但并非一定)使用 Netty。

          一些大數(shù)據(jù)基礎(chǔ)設(shè)施,比如 Hadoop,在處理海量數(shù)據(jù)的時(shí)候,數(shù)據(jù)在多個(gè)計(jì)算節(jié)點(diǎn)之中傳輸,為了提高傳輸性能,也采用 Netty 構(gòu)建性能更高的網(wǎng)絡(luò) IO 層。

          在游戲行業(yè),Netty 被用于構(gòu)建高性能的游戲交互服務(wù)器,Netty 提供了 TCP/UDP、HTTP 協(xié)議棧,方便開(kāi)發(fā)者基于 Netty 進(jìn)行私有協(xié)議的開(kāi)發(fā)。

          ……

          Netty 作為成熟的高性能異步通信框架,無(wú)論是應(yīng)用在互聯(lián)網(wǎng)分布式應(yīng)用開(kāi)發(fā)中,還是在大數(shù)據(jù)基礎(chǔ)設(shè)施構(gòu)建中,亦或是用于實(shí)現(xiàn)應(yīng)用層基于公私協(xié)議的服務(wù)器等等,都有出色的表現(xiàn),是一個(gè)極好的輪子。

          1.3. Java 中的網(wǎng)絡(luò) IO 模型

          Java 中的網(wǎng)絡(luò) IO 模型有三種:BIO、NIO、AIO。

          1)BIO:同步的、阻塞式 IO。在這種模型中,服務(wù)器上一個(gè)線程處理一次連接,即客戶端每發(fā)起一個(gè)請(qǐng)求,服務(wù)端都要開(kāi)啟一個(gè)線程專門處理該請(qǐng)求。這種模型對(duì)線程量的耗費(fèi)極大,且線程利用率低,難以承受請(qǐng)求的高并發(fā)。BIO 雖然可以使用線程池+等待隊(duì)列進(jìn)行優(yōu)化,避免使用過(guò)多的線程,但是依然無(wú)法解決線程利用率低的問(wèn)題。

          使用 BIO 構(gòu)建 C/S 系統(tǒng)的 Java 編程組件是 ServerSocket 和 Socket。服務(wù)端示例代碼為:

          public?static?void?main(String[]?args)?throws?IOException?{
          ????ExecutorService?threadPool?=?Executors.newCachedThreadPool();
          ????ServerSocket?serverSocket?=?new?ServerSocket(8080);

          ????while?(true)?{
          ????????Socket?socket?=?serverSocket.accept();
          ????????threadPool.execute(()?->?{
          ????????????handler(socket);
          ????????});
          ????}
          }

          /**
          ?*?處理客戶端請(qǐng)求
          ?*/

          private?static?void?handler(Socket?socket)?throws?IOException?{
          ????byte[]?bytes?=?new?byte[1024];

          ????InputStream?inputStream?=?socket.getInputStream();
          ????socket.close();

          ????while?(true)?{
          ????????int?read?=?inputStream.read(bytes);
          ????????if?(read?!=?-1)?{
          ????????????System.out.println("msg?from?client:?"?+?new?String(bytes,?0,?read));
          ????????}?else?{
          ????????????break;
          ????????}
          ????}
          }

          2)NIO:同步的、非阻塞式 IO。在這種模型中,服務(wù)器上一個(gè)線程處理多個(gè)連接,即多個(gè)客戶端請(qǐng)求都會(huì)被注冊(cè)到多路復(fù)用器(后文要講的 Selector)上,多路復(fù)用器會(huì)輪訓(xùn)這些連接,輪訓(xùn)到連接上有 IO 活動(dòng)就進(jìn)行處理。NIO 降低了線程的需求量,提高了線程的利用率。Netty 就是基于 NIO 的(這里有一個(gè)問(wèn)題:前文大力宣揚(yáng) Netty 是一個(gè)異步高性能網(wǎng)絡(luò)應(yīng)用框架,為何這里又說(shuō) Netty 是基于同步的 NIO 的?請(qǐng)讀者跟著文章的描述找尋答案)。

          NIO 是面向緩沖區(qū)編程的,從緩沖區(qū)讀取數(shù)據(jù)的時(shí)候游標(biāo)在緩沖區(qū)中是可以前后移動(dòng)的,這就增加了數(shù)據(jù)處理的靈活性。這和面向流的 BIO 只能順序讀取流中數(shù)據(jù)有很大的不同。

          Java NIO 的非阻塞模式,使得一個(gè)線程從某個(gè)通道讀取數(shù)據(jù)的時(shí)候,若當(dāng)前有可用數(shù)據(jù),則該線程進(jìn)行處理,若當(dāng)前無(wú)可用數(shù)據(jù),則該線程不會(huì)保持阻塞等待狀態(tài),而是可以去處理其他工作(比如處理其他通道的讀寫);同樣,一個(gè)線程向某個(gè)通道寫入數(shù)據(jù)的時(shí)候,一旦開(kāi)始寫入,該線程無(wú)需等待寫完即可去處理其他工作(比如處理其他通道的讀寫)。這種特性使得一個(gè)線程能夠處理多個(gè)客戶端請(qǐng)求,而不是像 BIO 那樣,一個(gè)線程只能處理一個(gè)請(qǐng)求。

          使用 NIO 構(gòu)建 C/S 系統(tǒng)的 Java 編程組件是 Channel、Buffer、Selector。服務(wù)端示例代碼為:

          public?static?void?main(String[]?args)?throws?IOException?{
          ????ServerSocketChannel?serverSocketChannel?=?ServerSocketChannel.open();
          ????Selector?selector?=?Selector.open();

          ????//?綁定端口
          ????serverSocketChannel.socket().bind(new?InetSocketAddress(8080));

          ????//?設(shè)置?serverSocketChannel?為非阻塞模式
          ????serverSocketChannel.configureBlocking(false);

          ????//?注冊(cè)?serverSocketChannel?到?selector,關(guān)注?OP_ACCEPT?事件
          ????serverSocketChannel.register(selector,?SelectionKey.OP_ACCEPT);

          ????while?(true)?{
          ????????//?沒(méi)有事件發(fā)生
          ????????if?(selector.select(1000)?==?0)?{
          ????????????continue;
          ????????}

          ????????//?有事件發(fā)生,找到發(fā)生事件的?Channel?對(duì)應(yīng)的?SelectionKey?的集合
          ????????Set?selectionKeys?=?selector.selectedKeys();

          ????????Iterator?iterator?=?selectionKeys.iterator();
          ????????while?(iterator.hasNext())?{
          ????????????SelectionKey?selectionKey?=?iterator.next();

          ????????????//?發(fā)生?OP_ACCEPT?事件,處理連接請(qǐng)求
          ????????????if?(selectionKey.isAcceptable())?{
          ????????????????SocketChannel?socketChannel?=?serverSocketChannel.accept();

          ????????????????//?將?socketChannel?也注冊(cè)到?selector,關(guān)注?OP_READ
          ????????????????//?事件,并給?socketChannel?關(guān)聯(lián)?Buffer
          ????????????????socketChannel.register(selector,?SelectionKey.OP_READ,?ByteBuffer.allocate(1024));
          ????????????}

          ????????????//?發(fā)生?OP_READ?事件,讀客戶端數(shù)據(jù)
          ????????????if?(selectionKey.isReadable())?{
          ????????????????SocketChannel?channel?=?(SocketChannel)?selectionKey.channel();
          ????????????????ByteBuffer?buffer?=?(ByteBuffer)?selectionKey.attachment();
          ????????????????channel.read(buffer);

          ????????????????System.out.println("msg?form?client:?"?+?new?String(buffer.array()));
          ????????????}

          ????????????//?手動(dòng)從集合中移除當(dāng)前的?selectionKey,防止重復(fù)處理事件
          ????????????iterator.remove();
          ????????}
          ????}
          }

          3)AIO:異步非阻塞式 IO。在這種模型中,由操作系統(tǒng)完成與客戶端之間的 read/write,之后再由操作系統(tǒng)主動(dòng)通知服務(wù)器線程去處理后面的工作,在這個(gè)過(guò)程中服務(wù)器線程不必同步等待 read/write 完成。由于不同的操作系統(tǒng)對(duì) AIO 的支持程度不同,AIO 目前未得到廣泛應(yīng)用。因此本文對(duì) AIO 不做過(guò)多描述。

          使用 Java NIO 構(gòu)建的 IO 程序,它的工作模式是:主動(dòng)輪訓(xùn) IO 事件,IO 事件發(fā)生后程序的線程主動(dòng)處理 IO 工作,這種模式也叫做 Reactor 模式。使用 Java AIO 構(gòu)建的 IO 程序,它的工作模式是:將 IO 事件的處理托管給操作系統(tǒng),操作系統(tǒng)完成 IO 工作之后會(huì)通知程序的線程去處理后面的工作,這種模式也叫做 Proactor 模式。

          本節(jié)最后,討論一下網(wǎng)路 IO 中阻塞、非阻塞、異步、同步這幾個(gè)術(shù)語(yǔ)的含義和關(guān)系:

          • 阻塞:如果線程調(diào)用 read/write 過(guò)程,但 read/write 過(guò)程沒(méi)有就緒或沒(méi)有完成,則調(diào)用 read/write 過(guò)程的線程會(huì)一直等待,這個(gè)過(guò)程叫做阻塞式讀寫。

          • 非阻塞:如果線程調(diào)用 read/write 過(guò)程,但 read/write 過(guò)程沒(méi)有就緒或沒(méi)有完成,調(diào)用 read/write 過(guò)程的線程并不會(huì)一直等待,而是去處理其他工作,等到 read/write 過(guò)程就緒或完成后再回來(lái)處理,這個(gè)過(guò)程叫做阻塞式讀寫。

          • 異步:read/write 過(guò)程托管給操作系統(tǒng)來(lái)完成,完成后操作系統(tǒng)會(huì)通知(通過(guò)回調(diào)或者事件)應(yīng)用網(wǎng)絡(luò) IO 程序(其中的線程)來(lái)進(jìn)行后續(xù)的處理。

          • 同步:read/write 過(guò)程由網(wǎng)絡(luò) IO 程序(其中的線程)來(lái)完成。

          基于以上含義,可以看出:異步 IO 一定是非阻塞 IO;同步 IO 既可以是阻塞 IO、也可以是非阻塞 IO。

          1.4. Java NIO API 簡(jiǎn)單回顧

          BIO 以流的方式處理數(shù)據(jù),而 NIO 以緩沖區(qū)(也被叫做塊)的方式處理數(shù)據(jù),塊 IO 效率比流 IO 效率高很多。BIO 基于字符流或者字節(jié)流進(jìn)行操作,而 NIO 基于 Channel 和 Buffer 進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)或者從緩沖區(qū)寫入到通道。Selector 用于監(jiān)聽(tīng)多個(gè)通道上的事件(比如收到連接請(qǐng)求、數(shù)據(jù)達(dá)到等等),因此使用單個(gè)線程就可以監(jiān)聽(tīng)多個(gè)客戶端通道。如下圖所示:

          關(guān)于上圖,再進(jìn)行幾點(diǎn)說(shuō)明:

          • 一個(gè) Selector 對(duì)應(yīng)一個(gè)處理線程

          • 一個(gè) Selector 上可以注冊(cè)多個(gè) Channel

          • 每個(gè) Channel 都會(huì)對(duì)應(yīng)一個(gè) Buffer(有時(shí)候一個(gè) Channel 可以使用多個(gè) Buffer,這時(shí)候程序要進(jìn)行多個(gè) Buffer 的分散和聚集操作),Buffer 的本質(zhì)是一個(gè)內(nèi)存塊,底層是一個(gè)數(shù)組

          • Selector 會(huì)根據(jù)不同的事件在各個(gè) Channel 上切換

          • Buffer 是雙向的,既可以讀也可以寫,切換讀寫方向要調(diào)用 Buffer 的 flip()方法

          • 同樣,Channel 也是雙向的,數(shù)據(jù)既可以流入也可以流出

          1.4.1. 緩沖區(qū)(Buffer)

          緩沖區(qū)(Buffer)本質(zhì)上是一個(gè)可讀可寫的內(nèi)存塊,可以理解成一個(gè)容器對(duì)象,Channel 讀寫文件或者網(wǎng)絡(luò)都要經(jīng)由 Buffer。在 Java NIO 中,Buffer 是一個(gè)頂層抽象類,它的常用子類有(前綴表示該 Buffer 可以存儲(chǔ)哪種類型的數(shù)據(jù)):

          • ByteBuffer

          • CharBuffer

          • ShortBuffer

          • IntBuffer

          • LongBuffer

          • DoubleBuffer

          • FloatBuffer

          涵蓋了 Java 中除 boolean 之外的所有的基本數(shù)據(jù)類型。其中 ByteBuffer 支持類型化的數(shù)據(jù)存取,即可以往 ByteBuffer 中放 byte 類型數(shù)據(jù)、也可以放 char、int、long、double 等類型的數(shù)據(jù),但讀取的時(shí)候要做好類型匹配處理,否則會(huì)拋出 BufferUnderflowException。

          另外,Buffer 體系中還有一個(gè)重要的 MappedByteBuffer(ByteBuffer 的子類),可以讓文件內(nèi)容直接在堆外內(nèi)存中被修改,而如何同步到文件由 NIO 來(lái)完成。本文重點(diǎn)不在于此,有興趣的可以去探究一下 MappedByteBuffer 的底層原理。

          1.4.2. 通道(Channel)

          通道(Channel)是雙向的,可讀可寫。在 Java NIO 中,Buffer 是一個(gè)頂層接口,它的常用子類有:

          • FileChannel:用于文件讀寫

          • DatagramChannel:用于 UDP 數(shù)據(jù)包收發(fā)

          • ServerSocketChannel:用于服務(wù)端 TCP 數(shù)據(jù)包收發(fā)

          • SocketChannel:用于客戶端 TCP 數(shù)據(jù)包收發(fā)

          1.4.3. 選擇器(Selector)

          選擇器(Selector)是實(shí)現(xiàn) IO 多路復(fù)用的關(guān)鍵,多個(gè) Channel 注冊(cè)到某個(gè) Selector 上,當(dāng) Channel 上有事件發(fā)生時(shí),Selector 就會(huì)取得事件然后調(diào)用線程去處理事件。也就是說(shuō)只有當(dāng)連接上真正有讀寫等事件發(fā)生時(shí),線程才會(huì)去進(jìn)行讀寫等操作,這就不必為每個(gè)連接都創(chuàng)建一個(gè)線程,一個(gè)線程可以應(yīng)對(duì)多個(gè)連接。這就是 IO 多路復(fù)用的要義。

          Netty 的 IO 線程 NioEventLoop 聚合了 Selector,可以同時(shí)并發(fā)處理成百上千的客戶端連接,后文會(huì)展開(kāi)描述。

          在 Java NIO 中,Selector 是一個(gè)抽象類,它的常用方法有:

          public?abstract?class?Selector?implements?Closeable?{
          ????......
          ????
          ????/**
          ?????*?得到一個(gè)選擇器對(duì)象
          ?????*/

          ????public?static?Selector?open()?throws?IOException?{
          ????????return?SelectorProvider.provider().openSelector();
          ????}
          ????......

          ????/**
          ?????*?返回所有發(fā)生事件的?Channel?對(duì)應(yīng)的?SelectionKey?的集合,通過(guò)
          ?????*?SelectionKey?可以找到對(duì)應(yīng)的?Channel
          ?????*/

          ????public?abstract?Set?selectedKeys();
          ????......
          ????
          ????/**
          ?????*?返回所有?Channel?對(duì)應(yīng)的?SelectionKey?的集合,通過(guò)?SelectionKey
          ?????*?可以找到對(duì)應(yīng)的?Channel
          ?????*/

          ????public?abstract?Set?keys();
          ????......
          ????
          ????/**
          ?????*?監(jiān)控所有注冊(cè)的?Channel,當(dāng)其中的?Channel?有?IO?操作可以進(jìn)行時(shí),
          ?????*?將這些 Channel 對(duì)應(yīng)的 SelectionKey 找到。參數(shù)用于設(shè)置超時(shí)時(shí)間
          ?????*/

          ????public?abstract?int?select(long?timeout)?throws?IOException;
          ????
          ????/**
          ????*?無(wú)超時(shí)時(shí)間的?select?過(guò)程,一直等待,直到發(fā)現(xiàn)有?Channel?可以進(jìn)行
          ????*?IO?操作
          ????*/

          ????public?abstract?int?select()?throws?IOException;
          ????
          ????/**
          ????*?立即返回的?select?過(guò)程
          ????*/

          ????public?abstract?int?selectNow()?throws?IOException;
          ????......
          ????
          ????/**
          ????*?喚醒?Selector,對(duì)無(wú)超時(shí)時(shí)間的?select?過(guò)程起作用,終止其等待
          ????*/

          ????public?abstract?Selector?wakeup();
          ????......
          }

          在上文的使用 Java NIO 編寫的服務(wù)端示例代碼中,服務(wù)端的工作流程為:

          1)當(dāng)客戶端發(fā)起連接時(shí),會(huì)通過(guò) ServerSocketChannel 創(chuàng)建對(duì)應(yīng)的 SocketChannel。

          2)調(diào)用 SocketChannel 的注冊(cè)方法將 SocketChannel 注冊(cè)到 Selector 上,注冊(cè)方法返回一個(gè) SelectionKey,該 SelectionKey 會(huì)被放入 Selector 內(nèi)部的 SelectionKey 集合中。該 SelectionKey 和 Selector 關(guān)聯(lián)(即通過(guò) SelectionKey 可以找到對(duì)應(yīng)的 Selector),也和 SocketChannel 關(guān)聯(lián)(即通過(guò) SelectionKey 可以找到對(duì)應(yīng)的 SocketChannel)。

          4)Selector 會(huì)調(diào)用 select()/select(timeout)/selectNow()方法對(duì)內(nèi)部的 SelectionKey 集合關(guān)聯(lián)的 SocketChannel 集合進(jìn)行監(jiān)聽(tīng),找到有事件發(fā)生的 SocketChannel 對(duì)應(yīng)的 SelectionKey。

          5)通過(guò) SelectionKey 找到有事件發(fā)生的 SocketChannel,完成數(shù)據(jù)處理。

          以上過(guò)程的相關(guān)源碼為:

          /**
          *?SocketChannel?繼承?AbstractSelectableChannel
          */

          public?abstract?class?SocketChannel
          ????extends?AbstractSelectableChannel
          ????implements?ByteChannel,?
          ???????????????ScatteringByteChannel,?
          ???????????????GatheringByteChannel,?
          ???????????????NetworkChannel
          {
          ????......
          }

          public?abstract?class?AbstractSelectableChannel
          ????extends?SelectableChannel
          {
          ????......
          ????/**
          ?????*?AbstractSelectableChannel?中包含注冊(cè)方法,SocketChannel?實(shí)例
          ?????*?借助該注冊(cè)方法注冊(cè)到?Selector?實(shí)例上去,該方法返回?SelectionKey
          ?????*/

          ????public?final?SelectionKey?register(
          ????????//?指明注冊(cè)到哪個(gè)?Selector?實(shí)例
          ????????Selector?sel,?
          ????????//?ops?是事件代碼,告訴?Selector?應(yīng)該關(guān)注該通道的什么事件
          ????????int?ops,
          ????????//?附加信息?attachment
          ????????Object?att)?throws?ClosedChannelException?
          {
          ????????......
          ????}
          ????......
          }

          public?abstract?class?SelectionKey?{
          ????......

          ????/**
          ?????*?獲取該?SelectionKey?對(duì)應(yīng)的?Channel
          ?????*/

          ????public?abstract?SelectableChannel?channel();

          ????/**
          ?????*?獲取該?SelectionKey?對(duì)應(yīng)的?Selector
          ?????*/

          ????public?abstract?Selector?selector();
          ????......
          ????
          ????/**
          ?????*?事件代碼,上面的?ops?參數(shù)取這里的值
          ?????*/

          ????public?static?final?int?OP_READ?=?1?<0;
          ????public?static?final?int?OP_WRITE?=?1?<2;
          ????public?static?final?int?OP_CONNECT?=?1?<3;
          ????public?static?final?int?OP_ACCEPT?=?1?<4;
          ????......
          ????
          ????/**
          ?????*?檢查該?SelectionKey?對(duì)應(yīng)的?Channel?是否可讀
          ?????*/

          ????public?final?boolean?isReadable()?{
          ????????return?(readyOps()?&?OP_READ)?!=?0;
          ????}

          ????/**
          ?????*?檢查該?SelectionKey?對(duì)應(yīng)的?Channel?是否可寫
          ?????*/

          ????public?final?boolean?isWritable()?{
          ????????return?(readyOps()?&?OP_WRITE)?!=?0;
          ????}

          ????/**
          ?????*?檢查該?SelectionKey?對(duì)應(yīng)的?Channel?是否已經(jīng)建立起?socket?連接
          ?????*/

          ????public?final?boolean?isConnectable()?{
          ????????return?(readyOps()?&?OP_CONNECT)?!=?0;
          ????}

          ????/**
          ?????*?檢查該?SelectionKey?對(duì)應(yīng)的?Channel?是否準(zhǔn)備好接受一個(gè)新的?socket?連接
          ?????*/

          ????public?final?boolean?isAcceptable()?{
          ????????return?(readyOps()?&?OP_ACCEPT)?!=?0;
          ????}

          ????/**
          ?????*?添加附件(例如?Buffer)
          ?????*/

          ????public?final?Object?attach(Object?ob)?{
          ????????return?attachmentUpdater.getAndSet(this,?ob);
          ????}

          ????/**
          ?????*?獲取附件
          ?????*/

          ????public?final?Object?attachment()?{
          ????????return?attachment;
          ????}
          ????......
          }

          下圖用于輔助讀者理解上面的過(guò)程和源碼:

          首先說(shuō)明,本文以 Linux 系統(tǒng)為對(duì)象來(lái)研究文件 IO 模型和網(wǎng)絡(luò) IO 模型。

          1.5. 零拷貝技術(shù)

          注:本節(jié)討論的是 Linux 系統(tǒng)下的 IO 過(guò)程。并且對(duì)于零拷貝技術(shù)的講解采用了一種淺顯易懂但能觸及其本質(zhì)的方式,因?yàn)檫@個(gè)話題,展開(kāi)來(lái)講實(shí)在是有太多的細(xì)節(jié)要關(guān)注。

          在“將本地磁盤中文件發(fā)送到網(wǎng)絡(luò)中”這一場(chǎng)景中,零拷貝技術(shù)是提升 IO 效率的一個(gè)利器,為了對(duì)比出零拷貝技術(shù)的優(yōu)越性,下面依次給出使用直接 IO 技術(shù)、內(nèi)存映射文件技術(shù)、零拷貝技術(shù)實(shí)現(xiàn)將本地磁盤文件發(fā)送到網(wǎng)絡(luò)中的過(guò)程。

          1)直接 IO 技術(shù)

          使用直接 IO 技術(shù)實(shí)現(xiàn)文件傳輸?shù)倪^(guò)程如下圖所示。

          上圖中,內(nèi)核緩沖區(qū)是 Linux 系統(tǒng)的 Page Cahe。為了加快磁盤的 IO,Linux 系統(tǒng)會(huì)把磁盤上的數(shù)據(jù)以 Page 為單位緩存在操作系統(tǒng)的內(nèi)存里,這里的 Page 是 Linux 系統(tǒng)定義的一個(gè)邏輯概念,一個(gè) Page 一般為 4K。

          可以看出,整個(gè)過(guò)程有四次數(shù)據(jù)拷貝,讀進(jìn)來(lái)兩次,寫回去又兩次:磁盤-->內(nèi)核緩沖區(qū)-->Socket 緩沖區(qū)-->網(wǎng)絡(luò)。

          直接 IO 過(guò)程使用的 Linux 系統(tǒng) API 為:

          ssize_t?read(int?filedes,?void?*buf,?size_t?nbytes);
          ssize_t?write(int?filedes,?void?*buf,?size_t?nbytes);

          等函數(shù)。

          2)內(nèi)存映射文件技術(shù)

          使用內(nèi)存映射文件技術(shù)實(shí)現(xiàn)文件傳輸?shù)倪^(guò)程如下圖所示。

          可以看出,整個(gè)過(guò)程有三次數(shù)據(jù)拷貝,不再經(jīng)過(guò)應(yīng)用程序內(nèi)存,直接在內(nèi)核空間中從內(nèi)核緩沖區(qū)拷貝到 Socket 緩沖區(qū)。

          內(nèi)存映射文件過(guò)程使用的 Linux 系統(tǒng) API 為:

          void?*mmap(void?*addr,?size_t?length,?int?prot,?int?flags,?int?fd,?off_t?offset);

          3)零拷貝技術(shù)

          使用零拷貝技術(shù),連內(nèi)核緩沖區(qū)到 Socket 緩沖區(qū)的拷貝也省略了,如下圖所示:

          內(nèi)核緩沖區(qū)到 Socket 緩沖區(qū)之間并沒(méi)有做數(shù)據(jù)的拷貝,只是一個(gè)地址的映射。底層的網(wǎng)卡驅(qū)動(dòng)程序要讀取數(shù)據(jù)并發(fā)送到網(wǎng)絡(luò)上的時(shí)候,看似讀取的是 Socket 的緩沖區(qū)中的數(shù)據(jù),其實(shí)直接讀的是內(nèi)核緩沖區(qū)中的數(shù)據(jù)。

          零拷貝中所謂的“零”指的是內(nèi)存中數(shù)據(jù)拷貝的次數(shù)為 0。

          零拷貝過(guò)程使用的 Linux 系統(tǒng) API 為:

          ssize_t?sendfile(int?out_fd,?int?in_fd,?off_t?*offset,?size_t?count);

          在 JDK 中,提供的:

          FileChannel.transderTo(long?position,?long?count,?WritableByteChannel?target);

          方法實(shí)現(xiàn)了零拷貝過(guò)程,其中的第三個(gè)參數(shù)可以傳入 SocketChannel 實(shí)例。例如客戶端使用以上的零拷貝接口向服務(wù)器傳輸文件的代碼為:

          public?static?void?main(String[]?args)?throws?IOException?{
          ????SocketChannel?socketChannel?=?SocketChannel.open();
          ????socketChannel.connect(new?InetSocketAddress("127.0.0.1",?8080));
          ????String?fileName?=?"test.zip";

          ????//?得到一個(gè)文件?channel
          ????FileChannel?fileChannel?=?new?FileInputStream(fileName).getChannel();
          ????
          ????//?使用零拷貝?IO?技術(shù)發(fā)送
          ????long?transferSize?=?fileChannel.transferTo(0,?fileChannel.size(),?socketChannel);
          ????System.out.println("file?transfer?done,?size:?"?+?transferSize);
          ????fileChannel.close();
          }

          以上部分為第一章,學(xué)習(xí) Netty 需要的基礎(chǔ)知識(shí)。



          往期精彩推薦



          騰訊、阿里、滴滴后臺(tái)面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因?yàn)槟銢](méi)認(rèn)真看完這篇文章


          END


          關(guān)注作者微信公眾號(hào) —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識(shí)以及最新面試寶典


          你點(diǎn)的每個(gè)好看,我都認(rèn)真當(dāng)成了


          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動(dòng)力

          瀏覽 74
          點(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>
                  欧美草逼网 | 国产一级a毛一级a做免费高清视频 | 欧美视频偷拍 | 亚洲色图欧美电影 | 91视频专区|