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

          聽說有人想看NIO知識點,三歪找來了

          共 5079字,需瀏覽 11分鐘

           ·

          2020-08-13 19:13


          本文公眾號來源:一枝花算不算浪漫
          作者:小飛 & 花哥
          本文已收錄至我的GitHub

          前言

          目前網(wǎng)上關(guān)于Netty學(xué)習(xí)資料玲瑯滿目,不知如何下手,其實大家都是一樣的,學(xué)習(xí)方法和技巧都是總結(jié)出來的,我們在沒有找到很好的方法之前不如按部就班先從基礎(chǔ)開始,一般從總分總的漸進方式,既觀森林,又見草木。

          一、網(wǎng)絡(luò)編程基礎(chǔ)回顧

          1. Socket

          Socket本身有“插座”的意思,不是Java中特有的概念,而是一個語言無關(guān)的標準,任何可以實現(xiàn)網(wǎng)絡(luò)編程的編程語言都有Socket。在Linux環(huán)境下,用于表示進程間網(wǎng)絡(luò)通信的特殊文件類型,其本質(zhì)為內(nèi)核借助緩沖區(qū)形成的偽文件。既然是文件,那么理所當然的,我們可以使用文件描述符引用套接字。

          與管道類似的,Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使得讀寫套接字和讀寫文件的操作一致。區(qū)別是管道主要應(yīng)用于本地進程間通信,而套接字多應(yīng)用于網(wǎng)絡(luò)進程間數(shù)據(jù)的傳遞。

          可以這么理解:Socket就是網(wǎng)絡(luò)上的兩個應(yīng)用程序通過一個雙向通信連接實現(xiàn)數(shù)據(jù)交換的編程接口API。

          Socket通信的基本流程具體步驟如下所示:

          (1)服務(wù)端通過Listen開啟監(jiān)聽,等待客戶端接入。

          (2)客戶端的套接字通過Connect連接服務(wù)器端的套接字,服務(wù)端通過Accept接收客戶端連接。在connect-accept過程中,操作系統(tǒng)將會進行三次握手。

          (3)客戶端和服務(wù)端通過writeread發(fā)送和接收數(shù)據(jù),操作系統(tǒng)將會完成TCP數(shù)據(jù)的確認、重發(fā)等步驟。

          (4)通過close關(guān)閉連接,操作系統(tǒng)會進行四次揮手。

          針對Java編程語言,java.net包是網(wǎng)絡(luò)編程的基礎(chǔ)類庫。其中ServerSocketSocket是網(wǎng)絡(luò)編程的基礎(chǔ)類型。

          SeverSocket是服務(wù)端應(yīng)用類型。Socket是建立連接的類型。當連接建立成功后,服務(wù)器和客戶端都會有一個Socket對象示例,可以通過這個Socket對象示例,完成會話的所有操作。對于一個完整的網(wǎng)絡(luò)連接來說,Socket是平等的,沒有服務(wù)器客戶端分級情況。

          2. IO模型介紹

          對于一次IO操作,數(shù)據(jù)會先拷貝到內(nèi)核空間中,然后再從內(nèi)核空間拷貝到用戶空間中,所以一次read操作,會經(jīng)歷兩個階段:

          (1)等待數(shù)據(jù)準備

          (2)數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間

          基于以上兩個階段就產(chǎn)生了五種不同的IO模式。

          1. 阻塞IO:從進程發(fā)起IO操作,一直等待上述兩個階段完成,此時兩階段一起阻塞。
          2. 非阻塞IO:進程一直詢問IO準備好了沒有,準備好了再發(fā)起讀取操作,這時才把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。第一階段不阻塞但要輪詢,第二階段阻塞。
          3. 多路復(fù)用IO:多個連接使用同一個select去詢問IO準備好了沒有,如果有準備好了的,就返回有數(shù)據(jù)準備好了,然后對應(yīng)的連接再發(fā)起讀取操作,把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。兩階段分開阻塞。
          4. 信號驅(qū)動IO:進程發(fā)起讀取操作會立即返回,當數(shù)據(jù)準備好了會以通知的形式告訴進程,進程再發(fā)起讀取操作,把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。第一階段不阻塞,第二階段阻塞。
          5. 異步IO:進程發(fā)起讀取操作會立即返回,等到數(shù)據(jù)準備好且已經(jīng)拷貝到用戶空間了再通知進程拿數(shù)據(jù)。兩個階段都不阻塞。

          這五種IO模式不難發(fā)現(xiàn)存在這兩對關(guān)系:同步和異步、阻塞和非阻塞。那么稍微解釋一下:

          同步和異步

          • 同步: 同步就是發(fā)起一個調(diào)用后,被調(diào)用者未處理完請求之前,調(diào)用不返回。
          • 異步: 異步就是發(fā)起一個調(diào)用后,立刻得到被調(diào)用者的回應(yīng)表示已接收到請求,但是被調(diào)用者并沒有返回結(jié)果,此時我們可以處理其他的請求,被調(diào)用者通常依靠事件,回調(diào)等機制來通知調(diào)用者其返回結(jié)果。

          同步和異步的區(qū)別最大在于異步的話調(diào)用者不需要等待處理結(jié)果,被調(diào)用者會通過回調(diào)等機制來通知調(diào)用者其返回結(jié)果。

          阻塞和非阻塞

          • 阻塞: 阻塞就是發(fā)起一個請求,調(diào)用者一直等待請求結(jié)果返回,也就是當前線程會被掛起,無法從事其他任務(wù),只有當條件就緒才能繼續(xù)。
          • 非阻塞: 非阻塞就是發(fā)起一個請求,調(diào)用者不用一直等著結(jié)果返回,可以先去干其他事情。

          阻塞和非阻塞是針對進程在訪問數(shù)據(jù)的時候,根據(jù)IO操作的就緒狀態(tài)來采取的不同方式,說白了是一種讀取或者寫入操作方法的實現(xiàn)方式,阻塞方式下讀取或者寫入函數(shù)將一直等待,而非阻塞方式下,讀取或者寫入方法會立即返回一個狀態(tài)值。

          如果組合后的同步阻塞(blocking-IO)簡稱BIO、同步非阻塞(non-blocking-IO)簡稱NIO和異步非阻塞(asynchronous-non-blocking-IO)簡稱AIO又代表什么意思呢?

          • BIO (同步阻塞I/O模式): 數(shù)據(jù)的讀取寫入必須阻塞在一個線程內(nèi)等待其完成。這里使用那個經(jīng)典的燒開水例子,這里假設(shè)一個燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 叫一個線程停留在一個水壺那,直到這個水壺?zé)_,才去處理下一個水壺。但是實際上線程在等待水壺?zé)_的時間段什么都沒有做。
          • NIO(同步非阻塞): 同時支持阻塞與非阻塞模式,但這里我們以其同步非阻塞I/O模式來說明,那么什么叫做同步非阻塞?如果還拿燒開水來說,NIO的做法是叫一個線程不斷的輪詢每個水壺的狀態(tài),看看是否有水壺的狀態(tài)發(fā)生了改變,從而進行下一步的操作。
          • AIO(異步非阻塞I/O模型): 異步非阻塞與同步非阻塞的區(qū)別在哪里?異步非阻塞無需一個線程去輪詢所有IO操作的狀態(tài)改變,在相應(yīng)的狀態(tài)改變后,系統(tǒng)會通知對應(yīng)的線程來處理。對應(yīng)到燒開水中就是,為每個水壺上面裝了一個開關(guān),水燒開之后,水壺會自動通知我水燒開了。

          java 中的 BIO、NIOAIO理解為是 Java 語言在操作系統(tǒng)層面對這三種 IO 模型的封裝。程序員在使用這些 封裝API 的時候,不需要關(guān)心操作系統(tǒng)層面的知識,也不需要根據(jù)不同操作系統(tǒng)編寫不同的代碼,只需要使用Java的API就可以了。由此,為了使讀者對這三種模型有個比較具體和遞推式的了解,并且和本文主題NIO有個清晰的對比,下面繼續(xù)延伸。

          Java BIO

          BIO編程方式通常是是Java的上古產(chǎn)品,自JDK 1.0-JDK1.4就有的東西。編程實現(xiàn)過程為:首先在服務(wù)端啟動一個ServerSocket來監(jiān)聽網(wǎng)絡(luò)請求,客戶端啟動Socket發(fā)起網(wǎng)絡(luò)請求,默認情況下SeverSocket會建立一個線程來處理此請求,如果服務(wù)端沒有線程可用,客戶端則會阻塞等待或遭到拒絕。服務(wù)器實現(xiàn)模式為一個連接一個線程,即客戶端有連接請求時服務(wù)器端就需要啟動一個線程進行處理。大致結(jié)構(gòu)如下:


          如果要讓 BIO 通信模型能夠同時處理多個客戶端請求,就必須使用多線程(主要原因是 socket.accept()、socket.read()、 socket.write() 涉及的三個主要函數(shù)都是同步阻塞的),也就是說它在接收到客戶端連接請求之后為每個客戶端創(chuàng)建一個新的線程進行鏈路處理,處理完成之后,通過輸出流返回應(yīng)答給客戶端,線程銷毀。這就是典型的 一請求一應(yīng)答通信模型 。我們可以設(shè)想一下如果這個連接不做任何事情的話就會造成不必要的線程開銷,不過可以通過線程池機制改善,線程池還可以讓線程的創(chuàng)建和回收成本相對較低。使用線程池機制改善后的 BIO 模型圖如下:

          BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,是JDK1.4以前的唯一選擇,但程序直觀簡單易懂。Java BIO編程示例網(wǎng)上很多,這里就不進行coding舉例了,畢竟后面NIO才是重點。

          Java NIO

          NIO(New IO或者No-Blocking IO),從JDK1.4 開始引入的非阻塞IO,是一種非阻塞+ 同步的通信模式。這里的No Blocking IO用于區(qū)分上面的BIO。

          NIO本身想解決 BIO的并發(fā)問題,通過Reactor模式的事件驅(qū)動機制來達到Non Blocking的。當 socket 有流可讀或可寫入 socket 時,操作系統(tǒng)會相應(yīng)的通知應(yīng)用程序進行處理,應(yīng)用再將流讀取到緩沖區(qū)或?qū)懭氩僮飨到y(tǒng)。

          也就是說,這個時候,已經(jīng)不是一個連接就 要對應(yīng)一個處理線程了,而是有效的請求,對應(yīng)一個線程,當連接沒有數(shù)據(jù)時,是沒有工作線程來處理的。

          當一個連接創(chuàng)建后,不需要對應(yīng)一個線程,這個連接會被注冊到 多路復(fù)用器上面,所以所有的連接只需要一個線程就可以搞定,當這個線程中的多路復(fù)用器 進行輪詢的時候,發(fā)現(xiàn)連接上有請求的話,才開啟一個線程進行處理,也就是一個請求一個線程模式。

          NIO提供了與傳統(tǒng)BIO模型中的SocketServerSocket相對應(yīng)的SocketChannelServerSocketChannel兩種不同的套接字通道實現(xiàn),如下圖結(jié)構(gòu)所示。這里涉及的Reactor設(shè)計模式、多路復(fù)用Selector、Buffer等暫時不用管,后面會講到。

          NIO 方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局 限于應(yīng)用中,編程復(fù)雜,JDK1.4 開始支持。同時,NIO和普通IO的區(qū)別主要可以從存儲數(shù)據(jù)的載體、是否阻塞等來區(qū)分:

          NIO和普通IO區(qū)別.png

          Java AIO

          NIO 不同,當進行讀寫操作時,只須直接調(diào)用 API 的 readwrite 方法即可。這兩種方法均為異步的,對于讀操作而言,當有流可讀取時,操作系統(tǒng)會將可讀的流傳入 read 方 法的緩沖區(qū),并通知應(yīng)用程序;對于寫操作而言,當操作系統(tǒng)將 write 方法傳遞的流寫入完畢時,操作系統(tǒng)主動通知應(yīng)用程序。即可以理解為,read/write 方法都是異步的,完成后會主動調(diào)用回調(diào)函數(shù)。在 JDK7 中,提供了異步文件通道和異步套接字通道的實現(xiàn),這部分內(nèi)容被稱作 NIO.

          AIO 方式使用于連接數(shù)目多且連接比較長(重操作)的架構(gòu),比如相冊服務(wù)器,充分調(diào)用 OS 參與并發(fā)操作,編程比較復(fù)雜,JDK7 開始支持。

          目前來說 AIO 的應(yīng)用還不是很廣泛,Netty 之前也嘗試使用過 AIO,不過又放棄了。

          二、NIO核心組件介紹

          1. Channel

          NIO中,基本所有的IO操作都是從Channel開始的,Channel通過Buffer(緩沖區(qū))進行讀寫操作。

          read()表示讀取通道中數(shù)據(jù)到緩沖區(qū),write()表示把緩沖區(qū)數(shù)據(jù)寫入到通道。

          Channel和Buffer互相操作.png

          Channel有好多實現(xiàn)類,這里有三個最常用:

          • SocketChannel:一個客戶端發(fā)起TCP連接的Channel
          • ServerSocketChannel:一個服務(wù)端監(jiān)聽新連接的TCP Channel,對于每一個新的Client連接,都會建立一個對應(yīng)的SocketChannel
          • FileChannel:從文件中讀寫數(shù)據(jù)

          其中SocketChannelServerSocketChannel是網(wǎng)絡(luò)編程中最常用的,一會在最后的示例代碼中會有講解到具體用法。

          2. Buffer

          概念

          Buffer也被成為內(nèi)存緩沖區(qū),本質(zhì)上就是內(nèi)存中的一塊,我們可以將數(shù)據(jù)寫入這塊內(nèi)存,之后從這塊內(nèi)存中讀取數(shù)據(jù)。也可以將這塊內(nèi)存封裝成NIO Buffer對象,并提供一組常用的方法,方便我們對該塊內(nèi)存進行讀寫操作。

          Bufferjava.nio中被定義為抽象類:

          Buffer結(jié)構(gòu)體系.png

          我們可以將Buffer理解為一個數(shù)組的封裝,我們最常用的ByteBuffer對應(yīng)的數(shù)據(jù)結(jié)構(gòu)就是byte[]

          屬性

          Buffer中有4個非常重要的屬性:capacity、limit、position、mark

          Buffer中基本屬性.png
          • capacity屬性:容量,Buffer能夠容納的數(shù)據(jù)元素的最大值,在Buffer初始化創(chuàng)建的時候被賦值,而且不能被修改。

          capacity.png

          上圖中,初始化Buffer的容量為8(圖中從0~7,共8個元素),所以capacity = 8

          • limit屬性:代表Buffer可讀可寫的上限。

            • 寫模式下:limit 代表能寫入數(shù)據(jù)的上限位置,這個時候limit = capacity讀模式下:在Buffer完成所有數(shù)據(jù)寫入后,通過調(diào)用flip()方法,切換到讀模式,此時limit等于Buffer中實際已經(jīng)寫入的數(shù)據(jù)大小。因為Buffer可能沒有被寫滿,所以limit<=capacity
          • position屬性:代表讀取或者寫入Buffer的位置。默認為0。

            • 寫模式下:每往Buffer中寫入一個值,position就會自動加1,代表下一次寫入的位置。
            • 讀模式下:每往Buffer中讀取一個值,position就自動加1,代表下一次讀取的位置。

          NIO屬性概念.png

          從上圖就能很清晰看出,讀寫模式下capacity、limit、position的關(guān)系了。

          • mark屬性:代表標記,通過mark()方法,記錄當前position值,將position值賦值給mark,在后續(xù)的寫入或讀取過程中,可以通過reset()方法恢復(fù)當前position為mark記錄的值。

          這幾個重要屬性講完,我們可以再來回顧下:

          0 <= mark <= position <= limit <= capacity

          現(xiàn)在應(yīng)該很清晰這幾個屬性的關(guān)系了~

          Buffer常見操作

          創(chuàng)建Buffer
          • allocate(int capacity)
          ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          int?count?=?channel.read(buffer);

          例子中創(chuàng)建的ByteBuffer是基于堆內(nèi)存的一個對象。

          • wrap(array)

          wrap方法可以將數(shù)組包裝成一個Buffer對象:

          ByteBuffer?buffer?=?ByteBuffer.wrap("hello?world".getBytes());
          channel.write(buffer);
          • allocateDirect(int capacity)

          通過allocateDirect方法也可以快速實例化一個Buffer對象,和allocate很相似,這里區(qū)別的是allocateDirect創(chuàng)建的是基于堆外內(nèi)存的對象。

          堆外內(nèi)存不在JVM堆上,不受GC的管理。堆外內(nèi)存進行一些底層系統(tǒng)的IO操作時,效率會更高。

          Buffer寫操作

          Buffer寫入可以通過put()channel.read(buffer)兩種方式寫入。

          通常我們NIO的讀操作的時候,都是從Channel中讀取數(shù)據(jù)寫入Buffer,這個對應(yīng)的是Buffer寫操作。

          Buffer讀操作

          Buffer讀取可以通過get()channel.write(buffer)兩種方式讀入。

          還是同上,我們對Buffer的讀入操作,反過來說就是對Channel寫操作。讀取Buffer中的數(shù)據(jù)然后寫入Channel中。

          Channel和Buffer互相操作.png
          其他常見方法
          • rewind():重置position位置為0,可以重新讀取和寫入buffer,一般該方法適用于讀操作,可以理解為對buffer的重復(fù)讀。
          public?final?Buffer?rewind()?{
          ????position?=?0;
          ????mark?=?-1;
          ????return?this;
          }
          • flip():很常用的一個方法,一般在寫模式切換到讀模式的時候會經(jīng)常用到。也會將position設(shè)置為0,然后設(shè)置limit等于原來寫入的position。
          public?final?Buffer?flip()?{
          ????limit?=?position;
          ????position?=?0;
          ????mark?=?-1;
          ????return?this;
          }
          • clear():重置buffer中的數(shù)據(jù),該方法主要是針對于寫模式,因為limit設(shè)置為了capacity,讀模式下會出問題。
          public?final?Buffer?clear()?{
          ????position?=?0;
          ????limit?=?capacity;
          ????mark?=?-1;
          ????return?this;
          }
          • mark()&reset(): mark()方法是保存當前position到變量markz中,然后通過reset()方法恢復(fù)當前positionmark,實現(xiàn)代碼很簡單,如下:
          public?final?Buffer?mark()?{
          ????mark?=?position;
          ????return?this;
          }

          public?final?Buffer?reset()?{
          ????int?m?=?mark;
          ????if?(m?0)
          ????????throw?new?InvalidMarkException();
          ????position?=?m;
          ????return?this;
          }

          常用的讀寫方法可以用一張圖總結(jié)一下:

          Buffer讀寫操作.png

          3. Selector

          概念

          Selector是NIO中最為重要的組件之一,我們常常說的多路復(fù)用器就是指的Selector組件。Selector組件用于輪詢一個或多個NIO Channel的狀態(tài)是否處于可讀、可寫。通過輪詢的機制就可以管理多個Channel,也就是說可以管理多個網(wǎng)絡(luò)連接。

          Selector原理圖.png

          輪詢機制

          1. 首先,需要將Channel注冊到Selector上,這樣Selector才知道需要管理哪些Channel
          2. 接著Selector會不斷輪詢其上注冊的Channel,如果某個Channel發(fā)生了讀或?qū)懙臅r間,這個Channel就會被Selector輪詢出來,然后通過SelectionKey可以獲取就緒的Channel集合,進行后續(xù)的IO操作。

          輪詢機制.png

          屬性操作

          1. 創(chuàng)建Selector

          通過open()方法,我們可以創(chuàng)建一個Selector對象。

          Selector?selector?=?Selector.open();
          1. 注冊Channel到Selector中

          我們需要將Channel注冊到Selector中,才能夠被Selector管理。

          channel.configureBlocking(false);
          SelectionKey?key?=?channel.register(selector,?SelectionKey.OP_READ);

          某個Channel要注冊到Selector中,那么該Channel必須是非阻塞,所有上面代碼中有個configureBlocking()的配置操作。

          register(Selector selector, int interestSet)方法的第二個參數(shù),標識一個interest集合,意思是Selector對哪些事件感興趣,可以監(jiān)聽四種不同類型的事件:

          public?static?final?int?OP_READ?=?1?<0;
          public?static?final?int?OP_WRITE?=?1?<public?static?final?int?OP_CONNECT?=?1?<3;
          public?static?final?int?OP_ACCEPT?=?1?<4;
          • Connect事件 :連接完成事件( TCP 連接 ),僅適用于客戶端,對應(yīng) SelectionKey.OP_CONNECT。
          • Accept事件 :接受新連接事件,僅適用于服務(wù)端,對應(yīng) SelectionKey.OP_ACCEPT 。
          • Read事件 :讀事件,適用于兩端,對應(yīng) SelectionKey.OP_READ ,表示 Buffer 可讀。
          • Write事件 :寫時間,適用于兩端,對應(yīng) SelectionKey.OP_WRITE ,表示 Buffer 可寫。

          Channel觸發(fā)了一個事件,表明該時間已經(jīng)準備就緒:

          • 一個Client Channel成功連接到另一個服務(wù)器,成為“連接就緒”
          • 一個Server Socket準備好接收新進入的接,稱為“接收就緒”
          • 一個有數(shù)據(jù)可讀的Channel,稱為“讀就緒”
          • 一個等待寫數(shù)據(jù)的Channel,稱為”寫就緒“

          當然,Selector是可以同時對多個事件感興趣的,我們使用或運算即可組合多個事件:

          int?interestSet?=?SelectionKey.OP_READ?|?SelectionKey.OP_WRITE;

          Selector其他一些操作

          選擇Channel
          public?abstract?int?select()?throws?IOException;
          public?abstract?int?select(long?timeout)?throws?IOException;
          public?abstract?int?selectNow()?throws?IOException;

          當Selector執(zhí)行select()方法就會產(chǎn)生阻塞,等到注冊在其上的Channel準備就緒就會立即返回,返回準備就緒的數(shù)量。

          select(long timeout)則是在select()的基礎(chǔ)上增加了超時機制。selectNow()立即返回,不產(chǎn)生阻塞。

          有一點非常需要注意: select 方法返回的 int 值,表示有多少 Channel 已經(jīng)就緒。

          自上次調(diào)用select 方法后有多少 Channel 變成就緒狀態(tài)。如果調(diào)用 select 方法,因為有一個 Channel 變成就緒狀態(tài)則返回了 1 ;

          若再次調(diào)用 select 方法,如果另一個 Channel 就緒了,它會再次返回1。

          獲取可操作的Channel
          Set?selectedKeys?=?selector.selectedKeys();

          當有新增就緒的Channel,調(diào)用select()方法,就會將key添加到Set集合中。

          三、代碼示例

          前面鋪墊了這么多,主要是想讓大家能夠看懂NIO代碼示例,也方便后續(xù)大家來自己手寫NIO 網(wǎng)絡(luò)編程的程序。創(chuàng)建NIO服務(wù)端的主要步驟如下:

          1.?打開ServerSocketChannel,監(jiān)聽客戶端連接
          2.?綁定監(jiān)聽端口,設(shè)置連接為非阻塞模式
          3.?創(chuàng)建Reactor線程,創(chuàng)建多路復(fù)用器并啟動線程
          4.?將ServerSocketChannel注冊到Reactor線程中的Selector上,監(jiān)聽ACCEPT事件
          5.?Selector輪詢準備就緒的key
          6.?Selector監(jiān)聽到新的客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路
          7.?設(shè)置客戶端鏈路為非阻塞模式
          8.?將新接入的客戶端連接注冊到Reactor線程的Selector上,監(jiān)聽讀操作,讀取客戶端發(fā)送的網(wǎng)絡(luò)消息
          9.?異步讀取客戶端消息到緩沖區(qū)
          10.對Buffer編解碼,處理半包消息,將解碼成功的消息封裝成Task
          11.將應(yīng)答消息編碼為Buffer,調(diào)用SocketChannel的write將消息異步發(fā)送給客戶端

          NIOServer.java

          public?class?NIOServer?{


          ????private?static?Selector?selector;

          ????public?static?void?main(String[]?args)?{
          ????????init();
          ????????listen();
          ????}

          ????private?static?void?init()?{
          ????????ServerSocketChannel?serverSocketChannel?=?null;

          ????????try?{
          ????????????selector?=?Selector.open();

          ????????????serverSocketChannel?=?ServerSocketChannel.open();
          ????????????serverSocketChannel.configureBlocking(false);
          ????????????serverSocketChannel.socket().bind(new?InetSocketAddress(9000));
          ????????????serverSocketChannel.register(selector,?SelectionKey.OP_ACCEPT);
          ????????????System.out.println("NioServer?啟動完成");
          ????????}?catch?(IOException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}

          ????private?static?void?listen()?{
          ????????while?(true)?{
          ????????????try?{
          ????????????????selector.select();
          ????????????????Iterator?keysIterator?=?selector.selectedKeys().iterator();
          ????????????????while?(keysIterator.hasNext())?{
          ????????????????????SelectionKey?key?=?keysIterator.next();
          ????????????????????keysIterator.remove();
          ????????????????????handleRequest(key);
          ????????????????}
          ????????????}?catch?(Throwable?t)?{
          ????????????????t.printStackTrace();
          ????????????}
          ????????}
          ????}

          ????private?static?void?handleRequest(SelectionKey?key)?throws?IOException?{
          ????????SocketChannel?channel?=?null;
          ????????try?{
          ????????????if?(key.isAcceptable())?{
          ????????????????ServerSocketChannel?serverSocketChannel?=?(ServerSocketChannel)?key.channel();
          ????????????????channel?=?serverSocketChannel.accept();
          ????????????????channel.configureBlocking(false);
          ????????????????System.out.println("接受新的?Channel");
          ????????????????channel.register(selector,?SelectionKey.OP_READ);
          ????????????}

          ????????????if?(key.isReadable())?{
          ????????????????channel?=?(SocketChannel)?key.channel();
          ????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          ????????????????int?count?=?channel.read(buffer);
          ????????????????if?(count?>?0)?{
          ????????????????????System.out.println("服務(wù)端接收請求:"?+?new?String(buffer.array(),?0,?count));
          ????????????????????channel.register(selector,?SelectionKey.OP_WRITE);
          ????????????????}
          ????????????}

          ????????????if?(key.isWritable())?{
          ????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          ????????????????buffer.put("收到".getBytes());
          ????????????????buffer.flip();

          ????????????????channel?=?(SocketChannel)?key.channel();
          ????????????????channel.write(buffer);
          ????????????????channel.register(selector,?SelectionKey.OP_READ);
          ????????????}
          ????????}?catch?(Throwable?t)?{
          ????????????t.printStackTrace();
          ????????????if?(channel?!=?null)?{
          ????????????????channel.close();
          ????????????}
          ????????}
          ????}
          }

          NIOClient.java

          public?class?NIOClient?{

          ????public?static?void?main(String[]?args)?{
          ????????new?Worker().start();
          ????}

          ????static?class?Worker?extends?Thread?{
          ????????@Override
          ????????public?void?run()?{
          ????????????SocketChannel?channel?=?null;
          ????????????Selector?selector?=?null;
          ????????????try?{
          ????????????????channel?=?SocketChannel.open();
          ????????????????channel.configureBlocking(false);

          ????????????????selector?=?Selector.open();
          ????????????????channel.register(selector,?SelectionKey.OP_CONNECT);
          ????????????????channel.connect(new?InetSocketAddress(9000));
          ????????????????while?(true)?{
          ????????????????????selector.select();
          ????????????????????Iterator?keysIterator?=?selector.selectedKeys().iterator();
          ????????????????????while?(keysIterator.hasNext())?{
          ????????????????????????SelectionKey?key?=?keysIterator.next();
          ????????????????????????keysIterator.remove();

          ????????????????????????if?(key.isConnectable())?{
          ????????????????????????????System.out.println();
          ????????????????????????????channel?=?(SocketChannel)?key.channel();

          ????????????????????????????if?(channel.isConnectionPending())?{
          ????????????????????????????????channel.finishConnect();

          ????????????????????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          ????????????????????????????????buffer.put("你好".getBytes());
          ????????????????????????????????buffer.flip();
          ????????????????????????????????channel.write(buffer);
          ????????????????????????????}

          ????????????????????????????channel.register(selector,?SelectionKey.OP_READ);
          ????????????????????????}

          ????????????????????????if?(key.isReadable())?{
          ????????????????????????????channel?=?(SocketChannel)?key.channel();
          ????????????????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          ????????????????????????????int?len?=?channel.read(buffer);

          ????????????????????????????if?(len?>?0)?{
          ????????????????????????????????System.out.println("["?+?Thread.currentThread().getName()
          ????????????????????????????????????????+?"]收到響應(yīng):"?+?new?String(buffer.array(),?0,?len));
          ????????????????????????????????Thread.sleep(5000);
          ????????????????????????????????channel.register(selector,?SelectionKey.OP_WRITE);
          ????????????????????????????}
          ????????????????????????}

          ????????????????????????if(key.isWritable())?{
          ????????????????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);
          ????????????????????????????buffer.put("你好".getBytes());
          ????????????????????????????buffer.flip();

          ????????????????????????????channel?=?(SocketChannel)?key.channel();
          ????????????????????????????channel.write(buffer);
          ????????????????????????????channel.register(selector,?SelectionKey.OP_READ);
          ????????????????????????}
          ????????????????????}
          ????????????????}
          ????????????}?catch?(Exception?e)?{
          ????????????????e.printStackTrace();
          ????????????}?finally{
          ????????????????if(channel?!=?null){
          ????????????????????try?{
          ????????????????????????channel.close();
          ????????????????????}?catch?(IOException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}
          ????????????????}

          ????????????????if(selector?!=?null){
          ????????????????????try?{
          ????????????????????????selector.close();
          ????????????????????}?catch?(IOException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}
          ????}
          }

          打印結(jié)果:

          //?Server端
          NioServer?啟動完成
          接受新的?Channel
          服務(wù)端接收請求:你好
          服務(wù)端接收請求:你好
          服務(wù)端接收請求:你好

          //?Client端
          [Thread-0]收到響應(yīng):收到
          [Thread-0]收到響應(yīng):收到
          [Thread-0]收到響應(yīng):收到

          四、總結(jié)

          回顧一下使用 NIO 開發(fā)服務(wù)端程序的步驟:

          1. 創(chuàng)建 ServerSocketChannel 和業(yè)務(wù)處理線程池。
          2. 綁定監(jiān)聽端口,并配置為非阻塞模式。
          3. 創(chuàng)建 Selector,將之前創(chuàng)建的 ServerSocketChannel 注冊到 Selector 上,監(jiān)聽 SelectionKey.OP_ACCEPT
          4. 循環(huán)執(zhí)行 Selector.select()`` 方法,輪詢就緒的Channel`。
          5. 輪詢就緒的 Channel 時,如果是處于 OP_ACCEPT 狀態(tài),說明是新的客戶端接入,調(diào)用 ServerSocketChannel.accept 接收新的客戶端。
          6. 設(shè)置新接入的 SocketChannel 為非阻塞模式,并注冊到 Selector 上,監(jiān)聽 OP_READ
          7. 如果輪詢的 Channel 狀態(tài)是 OP_READ,說明有新的就緒數(shù)據(jù)包需要讀取,則構(gòu)造 ByteBuffer 對象,讀取數(shù)據(jù)。

          那從這些步驟中基本知道開發(fā)者需要熟悉的知識點有:

          1. jdk-nio提供的幾個關(guān)鍵類:Selector , SocketChannel , ServerSocketChannel , FileChannel ,ByteBuffer ,SelectionKey
          2. 需要知道網(wǎng)絡(luò)知識:tcp粘包拆包 、網(wǎng)絡(luò)閃斷、包體溢出及重復(fù)發(fā)送等
          3. 需要知道linux底層實現(xiàn),如何正確的關(guān)閉channel,如何退出注銷selector ,如何避免selector太過于頻繁
          4. 需要知道如何讓client端獲得server端的返回值,然后才返回給前端,需要如何等待或在怎樣作熔斷機制
          5. 需要知道對象序列化,及序列化算法
          6. 省略等等,因為我已經(jīng)有點不舒服了,作為程序員的我習(xí)慣了舒舒服服簡單的API,不用太知道底層細節(jié),就能寫出比較健壯和沒有Bug的代碼...

          NIO 原生 API 的弊端 :

          ① NIO 組件復(fù)雜 : 使用原生 NIO 開發(fā)服務(wù)器端與客戶端 , 需要涉及到 服務(wù)器套接字通道 ( ServerSocketChannel ) , 套接字通道 ( SocketChannel ) , 選擇器 ( Selector ) , 緩沖區(qū) ( ByteBuffer ) 等組件 , 這些組件的原理 和API 都要熟悉 , 才能進行 NIO 的開發(fā)與調(diào)試 , 之后還需要針對應(yīng)用進行調(diào)試優(yōu)化

          ② NIO 開發(fā)基礎(chǔ) : NIO 門檻略高 , 需要開發(fā)者掌握多線程、網(wǎng)絡(luò)編程等才能開發(fā)并且優(yōu)化 NIO 網(wǎng)絡(luò)通信的應(yīng)用程序

          ③ 原生 API 開發(fā)網(wǎng)絡(luò)通信模塊的基本的傳輸處理 : 網(wǎng)絡(luò)傳輸不光是實現(xiàn)服務(wù)器端和客戶端的數(shù)據(jù)傳輸功能 , 還要處理各種異常情況 , 如 連接斷開重連機制 , 網(wǎng)絡(luò)堵塞處理 , 異常處理 , 粘包處理 , 拆包處理 , 緩存機制 等方面的問題 , 這是所有成熟的網(wǎng)絡(luò)應(yīng)用程序都要具有的功能 , 否則只能說是入門級的 Demo

          ④ NIO BUG : NIO 本身存在一些 BUG , 如 Epoll , 導(dǎo)致 選擇器 ( Selector ) 空輪詢 , 在 JDK 1.7 中還沒有解決

          NettyNIO 的基礎(chǔ)上 , 封裝了 Java 原生的 NIO API , 解決了上述哪些問題呢 ?


          后面我們再來看看,關(guān)注三歪,不丟失精品

          各類知識點總結(jié)

          下面的文章都有對應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~

          掃碼或者微信搜Java3y?免費領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來問我。


          原創(chuàng)電子書

          原創(chuàng)思維導(dǎo)圖


          我是三歪,一個想要變強的男人,感謝大家的點贊收藏和轉(zhuǎn)發(fā),下期見。
          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  日韩毛片儿。 | 黑人操亚州人 | 精品人妻无码一区二区三区91 | 日韩久久久性爱 | 肏B人人操 |