OKOK,廢話不多說。我回來了。還記得刷完牛客哭得不行的我嗎。回首去年太慘了太慘了。好在今年7月份畢業到至今,結果還不算太慘。哈哈哈。 進入主題,進入主題,施主莫鬧,施主莫慌。
大兵長今天被人懟了!!(enmmm博主名稱大兵在,打錯字了不要在意細節。)
事出有因,聽我慢慢到來。
是這樣,今天在地鐵上,很擠很擠,一個大帥比給一個大漂亮美美講netty,那叫一個裝13,說的我差點都信了。
大兵長能忍?不能、于是上前說道,放開那個大漂亮,讓我先來 。
當時是這樣的,
前方高能:
妹妹:啊,你想干嘛。你能告訴我Netty是什么嗎?
大兵長:那,那,那必須滴。showTime.
可是,可是,腦子翻江倒海。只有妹妹的漂亮,Netty到底在哪里。呀。在這嘞。
我慢慢進入到里面,一步一步的進入。
呀,快進去了。我的知識海洋,看到了一丟丟。
一:趕緊介紹一下
1. Netty主要是用于服務器通訊相關的各種各類的場景。本質其實也就是一個NIO框架啦。
2. 你去Github上能發現,這就是一個獨立的項目,是由JBOSS提供的一個開源框架。 3. 還是異步嘞,基于事件驅動的一個賊牛逼,用于快速開發高性能,高可靠性的網絡IO程序。 主要是針對TCP協議,面向客戶端高并發應用。
妹妹:手,放哪兒呢。
大兵長:嘿嘿,不好意思。
妹妹:你說本質是NIO,那是不是想要深入了解和學習Netty就必須要先搞一下NIO啊。
大兵長:呦呵,你這個搞很有感覺,那我來給你搞一下NIO.
妹妹:好呀好呀。搞好了,有獎勵哦。
! [ 請添加圖片描述
] ( https://img-blog.csdnimg.cn/e6bbce9d1a5c44c39b3bc7815a7ce81b.png
)
廢話少說,妹妹都說了,直接走起,進入更深,一眼望去,竟然是黑森林。看來NIO要來了。 不對不對,這之前先從0開始吧。
二:I/O模型
其實IO模型這玩意兒,妹妹應該是聽過的。但還是要給他講一下。畢竟妹妹學過JAVA基礎的嗎。
1:IO大家都知道,其實也就是進進出出嗎。IO模型也就是進出的模型啦。 2:IO模型:咱們用啥通道進行這個數據的進出(發送和接收)。 3:現在JAVA支持的有三種網絡編程模型IO模式:BIO,AIO,NIO
行吧,不喜歡深入的男人不是好男銀。 (1)BIO是個啥 BIO,blockIO,block阻塞,同步的IO模型。 傳統滴模型,一個連接對應一個線程滴。(海底撈,你吃飯,服務員小姐姐只盯著你,啥也不做,為你服務撒!! you see? you see?)客戶端有連接請求(你吃飯),服務器端(海底撈)就要啟動一個線程(服務員小姐姐)進行處理。你不吃,人家一直盯著你,小姐姐本來可以做其他事,結果你來了,啥也不做,人家不就浪費時間在你身上了嗎。(造成不必要的線程開銷)。
(2)NIO是個啥 NIO:是一個同步非阻塞的模型,服務器實現的模式是一個線程處理好多個請求。為啥會這樣,因為多了個多路復用器 ,這個玩意兒就牛逼格拉斯了,他會不斷的輪詢到連接有IO請求的就會去處理。(海底撈(服務器)搞了一個紅領巾小分隊,在那一會兒看看這看看哪兒。你想加酸梅湯,他就過來給你加,然后他再回去小分隊帶著,等待別人是否需要加酸梅湯啦。)
(3)AIO是個啥 AIO啊其實是NIO的加強版也就是NIOplus,異步非阻塞,AIO 引入異步通道的概念,采用了 Proactor 模式,簡化了程序編寫,有效的請求才啟動線程,它的特點是先由操作系統完成后才通知服務端程序啟動線程去處理,一般適用于連接數較多且連接時間較長的應用。
他們都用在哪里?
應用場景何在?
1 :BIO同步阻塞,是同步的且阻塞的,一般用在連接數較少的情況下。而且對服務器資源要求較高。
1 對1嗎。線程消耗較多啦。jdk1.4之前只能用它了。并發還有局限,你懂得。
2 :NIO同步非阻塞。JDK1.4之后都用它。他牛逼啊,連接數多連接短,就是不要一直連接嗎。還要接受其他請求呢。像我們聊天服務器,服務間通信等,這都是短時連接。所以用它比較好。
3 :AIO nioplus版本。jdk7才開始支持的,但是很少有,多用于連接數多連接長。
什么啊?你要我寫一個BIO的例子給你? 可不可以不寫啊,算了看你好看。還是寫一個吧。 你看這是流程,很明顯我們能知道以下幾件事。 1:我們要創建一個服務端吧(海底撈總部) 2:也要搞幾個客戶吧。(比如大兵長這個逼) 3:也要搞幾個線程吧(服務員),如果有能分配給你的服務員,就給你加酸梅湯唄。不然就只能讓你等了。
package com. lidadaibiao. biotest ; import java. io. IOException ;
import java. io. InputStream ;
import java. net. ServerSocket ;
import java. net. Socket ;
import java. util. concurrent. * ;
public class HAIDILAOServer { public static void main ( String [ ] args
) throws IOException { ThreadPoolExecutor fuwuyuanPool
= new ThreadPoolExecutor ( 5 , 20 , 2 , TimeUnit . SECONDS
, new LinkedBlockingQueue < > ( 5 ) ) ; ServerSocket haidilaoServer
= new ServerSocket ( 6666 ) ; System . out
. println ( "海底撈分部 開業啦。(服務器啟動~~~~~~~~~)" ) ; while ( true ) { System . out
. println ( "海底撈分部(線程)信息id = " + Thread . currentThread ( ) . getId ( ) + "名字 = " + Thread . currentThread ( ) . getName ( ) ) ; System . out
. println ( "等待連接...." ) ; final Socket socket
= haidilaoServer
. accept ( ) ; System . out
. println ( "呦呵,一個客戶來了~~~" ) ; fuwuyuanPool
. execute ( new Runnable ( ) { @Override public void run ( ) { fuwu ( socket
) ; } } ) ; } } private static void fuwu ( Socket socket
) { try { System . out
. println ( "客戶(線程)信息id = " + Thread . currentThread ( ) . getId ( ) + "名字 = " + Thread . currentThread ( ) . getName ( ) ) ; byte [ ] bytes
= new byte [ 1024 ] ; InputStream inputStream
= socket
. getInputStream ( ) ; while ( true ) { System . out
. println ( "客戶(線程)信息id = " + Thread . currentThread ( ) . getId ( ) + "名字 = " + Thread . currentThread ( ) . getName ( ) ) ; System . out
. println ( "read...." ) ; int read
= inputStream
. read ( bytes
) ; if ( read
!= - 1 ) { System . out
. println ( new String ( bytes
, 0 , read
) ) ; } else { break ; } } } catch ( Exception e
) { e
. printStackTrace ( ) ; } finally { } } }
總結一下流程哈: 1:也就是服務器端啟動了一個ServerSocket。 2:客戶端啟動了Socket發起通信,服務器端就會搞一個線程出來處理它。 3:客戶端會先問一下有沒有線程,么有就等待咯。 PS:這里是用CMD telnet方式處理的。 這里很明顯啦: BIO問題多多,1對1,每次請求都要創建一個線程,海底撈不要虧死哦。服務器性能賊低。人多的時候(并發高)海底撈要找大量的服務員,頂不住哦。連接建立后,客戶不動,服務員不動。就在哪兒干等這。(線程阻塞在read上面了。線程資源浪費咯。)
美女:喂~~~ 你到底知不知道NIO是個啥。在這BB啥哦。
大兵長:呦呵 敢嘲諷我,下面就給你好好講講NIO是個什么鬼。
三:NIO編程詳解。
1:NIO就是這么個東西
JAVA non-blocking IO 同步非阻塞 JDK1.4以后提供的新API,那時產出一系列改進的輸入/輸出的新特性。即為NIO. 三大核心部分:Channel(通道),Buffer(緩沖區),Selector(選擇器) (我覺得你可能想讓我說一下) 面向緩沖區,面向塊的編程。這樣理解試試:數據來了,扔到一個緩沖區,需要時從緩沖區依次移動獲取。 相關類都在java.nio包下面,不信?一會兒非給你看看。
PS:通俗來說NIO就是可以那么牛逼。一個服務員服務多個客戶。如果一家海底撈有2000個顧客過來。我們就可以分成20個紅領巾小分隊。然后20個小分隊每個負責100個顧客,這個顧客需要,一個小分隊就去幫忙,不需要就不去回來待著,等待其他的顧客需要幫忙。相當于20個線程解決了2000個請求。不用像原先阻塞那樣,需要搞2000個線程。
四:你想聽的三大組件
你是不是想聽了?你對知識的渴望一定很高吧。現在都那么深入了,難道能停下來?要不關注一下? 先從總的說起吧。 啥子關系? 情侶?朋友?閨蜜?難道是***?咋想的。看圖中不? enmmm:第一張圖就是他三個在總流程里面的一個位置,第二張圖就是他們三者關系啦。 好了好了。總結一下
一個線程對應一個selecter(選擇器),一個selecter(選擇器)對應好多channel(通道),一個channel(通道)對應一個buffer(緩沖區) 具體用哪個通道channel這個是由事件確定的,Event. Selector會根據不同的事件,最終選擇不同的通道channel進行處理程序(客戶端/服務器端)。
1:Buffer(緩沖區) ok,我們從下往上步步逼近,最終拿下。 1.1緩沖區Buffer:
就是NIO中那個緩沖塊。本質上是一個可以讀寫數據的內存塊 ,底層是數組 。 數據的讀取寫入都是通過緩沖區Buffer的,相比BIO(輸入流,輸出流,單向),該Buffer可讀可寫,雙向,需要用flip方法 切換方向。 Buffer對象提供了一堆方法,能夠讓你爽死,灰常輕松的使用內存塊。 內置了一系列機制,能夠跟蹤和記錄緩沖區的狀態變化情況 。而且Channel提供從文件,網絡等獲取的數據,但是讀取和寫入都必須經過Buffer.
詳細點說:buffer和Channel之間其實是雙向關系。 PS:buffer是一個緩沖區,存放數據塊。channel是一個通道處理程序(客戶端/服務器端),也就是說程序之間的數據,會從通道中讀取到緩沖區,從緩沖區中寫入到通道 內 1.2 Buffer API Buffer是一個父類,下面有很多子類。 其實我不想進去的,但是你非讓進,那我就沒辦法了。 buffer源碼:
package java. nio ;
import java. util. Spliterator ;
public abstract class Buffer { static final int SPLITERATOR_CHARACTERISTICS
= Spliterator . SIZED
| Spliterator . SUBSIZED
| Spliterator . ORDERED
; private int mark
= - 1 ; private int position
= 0 ; private int limit
; private int capacity
; long address
; Buffer ( int mark
, int pos
, int lim
, int cap
) { if ( cap
< 0 ) throw new IllegalArgumentException ( "Negative capacity: " + cap
) ; this . capacity
= cap
; limit ( lim
) ; position ( pos
) ; if ( mark
>= 0 ) { if ( mark
> pos
) throw new IllegalArgumentException ( "mark > position: (" + mark
+ " > " + pos
+ ")" ) ; this . mark
= mark
; } } public final int capacity ( ) { return capacity
; } public final int position ( ) { return position
; } public final Buffer position ( int newPosition
) { if ( ( newPosition
> limit
) || ( newPosition
< 0 ) ) throw new IllegalArgumentException ( ) ; position
= newPosition
; if ( mark
> position
) mark
= - 1 ; return this ; } public final int limit ( ) { return limit
; } public final Buffer limit ( int newLimit
) { if ( ( newLimit
> capacity
) || ( newLimit
< 0 ) ) throw new IllegalArgumentException ( ) ; limit
= newLimit
; if ( position
> limit
) position
= limit
; if ( mark
> limit
) mark
= - 1 ; return this ; } 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 ; } public final Buffer clear ( ) { position
= 0 ; limit
= capacity
; mark
= - 1 ; return this ; } public final Buffer flip ( ) { limit
= position
; position
= 0 ; mark
= - 1 ; return this ; } public final Buffer rewind ( ) { position
= 0 ; mark
= - 1 ; return this ; } public final int remaining ( ) { return limit
- position
; } public final boolean hasRemaining ( ) { return position
< limit
; } public abstract boolean isReadOnly ( ) ; public abstract boolean hasArray ( ) ; public abstract Object array ( ) ; public abstract int arrayOffset ( ) ; public abstract boolean isDirect ( ) ; final int nextGetIndex ( ) { if ( position
>= limit
) throw new BufferUnderflowException ( ) ; return position
++ ; } final int nextGetIndex ( int nb
) { if ( limit
- position
< nb
) throw new BufferUnderflowException ( ) ; int p
= position
; position
+= nb
; return p
; } final int nextPutIndex ( ) { if ( position
>= limit
) throw new BufferOverflowException ( ) ; return position
++ ; } final int nextPutIndex ( int nb
) { if ( limit
- position
< nb
) throw new BufferOverflowException ( ) ; int p
= position
; position
+= nb
; return p
; } final int checkIndex ( int i
) { if ( ( i
< 0 ) || ( i
>= limit
) ) throw new IndexOutOfBoundsException ( ) ; return i
; } final int checkIndex ( int i
, int nb
) { if ( ( i
< 0 ) || ( nb
> limit
- i
) ) throw new IndexOutOfBoundsException ( ) ; return i
; } final int markValue ( ) { return mark
; } final void truncate ( ) { mark
= - 1 ; position
= 0 ; limit
= 0 ; capacity
= 0 ; } final void discardMark ( ) { mark
= - 1 ; } static void checkBounds ( int off
, int len
, int size
) { if ( ( off
| len
| ( off
+ len
) | ( size
- ( off
+ len
) ) ) < 0 ) throw new IndexOutOfBoundsException ( ) ; }
}
當然作為一個基類:下面還有很多子類,而且他們本身會有自己額外的方法。這個看下源碼就很明確了。這里只簡單看一下。就不多說啦。有興趣可以看下源碼。
2:Channel(通道) 我們都知道,BIO是以流的形式去處理數據的,但是NIO是以塊的形式處理數據的。明顯塊要比流的形式效率高很多。 所以我理解的NIO其實基于通道和緩沖區的。通道相當于一個河流,隧道之類的角色。選擇器(Selector根據事件去選擇一個通道去處理程序的時候),其實是將程序發出的數據或者操作請求,通過通道讀取到緩沖區,回應給程序或者發送給程序數據和操作請求時,通道從緩沖區中寫入數據,最終給程序。 2.1Channel(通道) :
通道是可以同時讀寫的 通道從緩沖區讀取數據,也可以寫入數據到緩沖區。是雙向的。 通道可以異步讀寫。 Channel是一個接口,常用的Channel類有ServerSocketChannel 和 SocketChannel,FileChaneel等。
2.2Channel相關API 2.2Channel相關API使用 以FileChannel為例 對于文件的復制,平時我們都是使用輸入輸出流進行操作,利用源文件創建出一個輸入流,然后利用目標文件創建出一個輸出流,最后將輸入流的數據讀取寫入到輸出流中。這樣也是可以進行操作的。但是利用fileChannel是很有用的一個方式。它能直接連接輸入輸出流的文件通道,將數據直接寫入到目標文件中去。而且效率更高。 利用上述ByteBuffer緩沖區和FileChannel通道完成一個復制文件的demon
ByteBuffer byteBuffer
= ByteBuffer . allocate ( 1024 ) ; fileChannelInput
. read ( byteBuffer
) ; byteBuffer
. clear ( ) ; String str
= "漂亮妹妹想要寫的數據內容" ; byteBuffer
. put ( str
. getBytes ( ) ) ; byteBuffer
. flip ( ) ; while ( byteBuffer
. hasRemaining ( ) ) fileChannelOutput
. write ( byteBuffer
) ; fileChannelInput
. position ( ) ; fileChannelInput
. size ( ) ; fileChannelInput
. truncate ( 1024 ) ; fileChannelInput
. force ( true ) ;
以下是一個利用通道進行文件copy
public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道
public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道
public static void main ( String [ ] args
) throws Exception { FileInputStream fileInputStream
= new FileInputStream ( "C:\\dabingzhang.txt" ) ; FileOutputStream fileOutputStream
= new FileOutputStream ( "C:\\dabingzhang2.txt" ) ; FileChannel sourceCh
= fileInputStream
. getChannel ( ) ; FileChannel destCh
= fileOutputStream
. getChannel ( ) ; destCh
. transferFrom ( sourceCh
, 0 , sourceCh
. size ( ) ) ; sourceCh
. close ( ) ; destCh
. close ( ) ; fileInputStream
. close ( ) ; fileOutputStream
. close ( ) ; }
3:Selector(選擇器) ok,開始之前我們還是把圖扔出來, 2.1Selector(選擇器/多路復用器):
從圖可知,多個通道對應一個Selector(選擇器),而這個是NIO非阻塞IO方式的關鍵。Selector就是多路復用器。 可以同時并發處理上千上萬個請求。 當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務 有了多路復用器(Selector),就實現了一個線程管理多個Channel,管理了多個請求和連接。 只有Channel連接中有讀寫發生,這個時候才會去進行處理,并不會為每一個連接都創建一個線程 減少了線程創建,線程上下文切換,CPU性能開銷。
2.2Selector API: 方法比較少,直接進源碼
public abstract class Selector implements Closeable { protected Selector ( ) { } public static Selector open ( ) throws IOException { return SelectorProvider . provider ( ) . openSelector ( ) ; } public abstract boolean isOpen ( ) ; public abstract SelectorProvider provider ( ) ; public abstract Set < SelectionKey > keys ( ) ; public abstract Set < SelectionKey > selectedKeys ( ) ; public abstract int selectNow ( ) throws IOException ; public abstract int select ( ) throws IOException ; public abstract int select ( long timeout
) throws IOException ; public abstract Selector wakeup ( ) ; public abstract void close ( ) throws IOException ; }
下面繼續用一個更詳細的圖,來描述一下。 這里要梳理一下,我們從Channel下面可以得到ServerSocketChannel,SocketChannel其實兩者對應的本身ServerSocket和Socket.
建立服務器端,然后監視客戶端的連接。客戶端連接的時候我們可以通過ServerSocketChannel去得到SocketChannel Selector 進行監聽 select 方法,返回有事件發生的通道的個數 將 socketChannel 注冊到 Selector 上,register(Selector sel, int ops),一個 Selector 上可以注冊多個 SocketChannel。 注冊后返回一個 SelectionKey,會和該 Selector 關聯(集合)。 進一步得到各個 SelectionKey(有事件發生)。 在通過 SelectionKey 反向獲取 SocketChannel,方法 channel()。 可以通過得到的 channel,完成業務處理。
妹妹:叮咚,我下車了,你別再深入了。下次再給講講吧。
大兵長:那我出來,今天不深入了。下次見面繼續啊。
SelectionKey可參考:https://blog.csdn.net/u011784767/article/details/74750153
總結
以上是生活随笔 為你收集整理的你这还不精通NIO(Netty_1) 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。