Netty基礎

目錄

 

1 背景知識

1.1 同步/異步 阻塞/非阻塞

1.2 Unix 五種 I/O 模型

1.3 Java NIO

1.4 Netty疑問

2 Netty

2.1 Netty組件

2.1 Channel

2.2 回調&Future

2.3 事件和ChannelHandler

2.4 eventLoopGroup eventLoop

3 problem

3.1 其他

3.1.1 池化

3.1.2 發送大文件

9 胡思亂想

9.3 Netty與零拷貝

0 參考資料


1 背景知識

1.1 同步/異步 阻塞/非阻塞

同步和異步指的是調用結果的返回方式,到底是自己去獲取還是,調用完成,內核來通知。

阻塞和非阻塞指的是調用者在調用時,自己本身的狀態。自身如果是一直處於等待狀態則是阻塞的,反之,則是非阻塞的。

 

同步

異步

阻塞

 

 

非阻塞

 

 

 

https://www.zhihu.com/question/19732473

1.2 Unix 五種 I/O 模型

https://juejin.im/post/58bbaee6ac502e006b02f607

可以從上表來分析Unix的五種I/O模型,

https://www.zhihu.com/question/19732473/answer/26101328

UNIX下可用的I/O模型:

  • 阻塞式I/O;

  • 非阻塞式I/O;

  • I/O複用(select,poll,epoll...);

  • 信號驅動式I/O(SIGIO);

  • 異步I/O(POSIX的aio_系列函數);

     

    阻塞式I/O模型:默認情況下,所有套接字都是阻塞的。怎麼理解?先理解這麼個流程,一個輸入操作通常包括兩個不同階段:

  • (1)等待數據準備好;

  • (2)從內核向進程複製數據。

    對於一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所有等待分組到達時,它被複制到內核中的某個緩衝區。第二步就是把數據從內核緩衝區複製到應用程序緩衝區。 好,下面我們以阻塞套接字的recvfrom的的調用圖來說明阻塞

    標紅的這部分過程就是阻塞,直到阻塞結束recvfrom才能返回。

    非阻塞式I/O: 以下這句話很重要:進程把一個套接字設置成非阻塞是在通知內核,當所請求的I/O操作非得把本進程投入睡眠才能完成時,不要把進程投入睡眠,而是返回一個錯誤。看看非阻塞的套接字的recvfrom操作如何進行

    可以看出recvfrom總是立即返回。

    I/O多路複用:雖然I/O多路複用的函數也是阻塞的,但是其與以上兩種還是有不同的,I/O多路複用是阻塞在select,epoll這樣的系統調用之上,而沒有阻塞在真正的I/O系統調用如recvfrom之上。如圖

  •  

    信號驅動式I/O:用的很少,就不做講解了。直接上圖

  •  

    異步I/O:這類函數的工作機制是告知內核啓動某個操作,並讓內核在整個操作(包括將數據從內核拷貝到用戶空間)完成後通知我們。如圖:

  •  

    注意紅線標記處說明在調用時就可以立馬返回,等函數操作完成會通知我們。等等,大家一定要問了,同步這個概念你怎麼沒涉及啊?別急,您先看總結。

    其實前四種I/O模型都是同步I/O操作,他們的區別在於第一階段,而他們的第二階段是一樣的:在數據從內核複製到應用緩衝區期間(用戶空間),進程阻塞於recvfrom調用。相反,異步I/O模型在這兩個階段都要處理。

  • 再看POSIX對這兩個術語的定義:同步I/O操作:導致請求進程阻塞,直到I/O操作完成;異步I/O操作:不導致請求進程阻塞。好,下面我用我的語言來總結一下阻塞,非阻塞,同步,異步阻塞,非阻塞:進程/線程要訪問的數據是否就緒,進程/線程是否需要等待;同步,異步:訪問數據的方式,同步需要主動讀寫數據,在讀寫數據的過程中還是會阻塞;異步只需要I/O操作完成的通知,並不主動讀寫數據,由操作系統內核完成數據的讀寫

我們現在常說的異步大多指的是應用層面的異步,實際上大多是I/O多路複用,這種方式實際上也是阻塞的,但是他是阻塞在系統調用select上,在一個線程裏面可以監聽多個I/O設備,也就是說只需要阻塞在一個線程之上即可。

recvfrom將數據從系統空間拷貝到用戶空間,這部必然是阻塞的。

 

1.3 Java NIO

最早的jdk提供的是基於Java的所有Socket通信都採用了同步阻塞模式(BIO)

jdk1.4 JDK的selector基於select/poll模型,基於I/O複用的非阻塞I/O

在JDK1.5 update 10 和Linux core2.6以上版本,優化了selector的實現,在底層使用epoll替換了select/poll,上層API沒有變化,可認爲是JDK NIO的一次性能優化。

2011年JDK1.7發佈,將原來的NIO進行了升級,通過JSR-203演進而來,稱爲NIO2.0,提供異步I/O開發類庫。

note: Google 併發包不是差不多嗎

 

1.4 Netty疑問

Q:Netty到底解決了什麼問題?

A: Netty解決了不同節點之間的網絡傳輸的可靠性與效率問題。

Q: Tcp不是已經保證了相對的可靠傳輸了嗎?Netty進一步保證了哪些具體的問題?

A: 【TODO】

Q:Netty的效率的實現原理是什麼?

A:Netty NIO 注:以上兩點都不是Netty首創的新技術,但是Netty使用他們提供了一套簡單易用可靠高效的網絡開發模型。

Q:Netty和tcp和Http這些協議之間是什麼關係?

A:Netty是網絡通信框架,但是並不是網絡通信協議。Netty的數據層協議可以使用tcp也可以使用udp。至於傳輸層協議Netty支持多種,更是支持自定義協議。非常靈活。

Q: 爲什麼需要自定義協議?使用Http協議會在什麼場景不適合?

A: Http協議是一種通用的傳輸層協議,協議內容複雜,自定義協議更加靈活,可以根據自己的場景需要做的更加複雜或者更加簡單。

 

 

 

 

2 Netty

2.1 Netty組件

2.1 Channel

一個文件或者設備或者網絡套接字,個人感覺類似Linux中句柄的概念。

2.2 回調&Future

Note:回調和Future的區別,回調需要自己手動去檢查轉態

2.3 事件和ChannelHandler

ChannelHandler體現的是事件驅動,定義了一系列的事件。

入站:對於一個客戶端而言,如果事件的流動方向是從服務器端到客戶端,則是 入站(讀操作),反之則是出站(寫操作)

 

這樣使事件流經ChannelPipeline是ChannelHandler的工作,它們是在應用程序初始化或引導階段被安裝的。這些對象接收事件、執行它們所實現的處理邏輯,並將數據傳遞給鏈中下一個ChannelHandler。它們的執行順序是由它們被添加順序所決定的。實際上,被我們稱爲ChannelPipeline的是這些ChannelHandler的編排順序。

圖3 說明了一個Netty應用程序中入站和出站數據流之間的區別。從一個客戶端應用程序的角度來看,如果事件的運動方向是從客戶端到服務端,那麼我們稱這些事件爲出站的,反之則稱爲入站的。

圖3還顯示了入站和出站HannelHandler可以被安裝到同一個ChannelPipeline中。如果一個消息或者任何其他的入站事件被讀取,那麼它會從ChannelPipeline的頭部開始流動,並被傳遞給第一個ChannelInboundHandler。這個ChannelHandler不一定會實際地修改數據,具體取決於它的具體功參,在這之後,數據將會被傳遞給鏈中的下一個ChannelInboundHandler。最終,數居將會能不能達ChannelPipeline的尾端,刷時,所有處理就都結束了。Netty會區分ChannelInBoundHandler實現和ChannelOutBoundHandler實現,並確保數據只會在相同定向類型的兩個ChannelHandler之間傳遞。

Netty提供許多類型的ChannelHandler,它們各自的功能主要取決於它們的超類。Netty以適配器的形式提供了大量默認的ChannelHandler實現,其旨在簡化商業應用程序處理邏輯開發過程。如圖3所示,ChannelPiepline中的每一個ChannelHandler將負責把事件轉發到鏈中的下一個ChannelHandler。這些適配器(及它們的子類)將自動執行這些操作,所以你可以重寫那些你想要特殊處理的方法和事件。

使用適配器的目的是將編寫自定義的ChannelHandler所需要的努力降到最低限度,因爲它提供了定義在對應接口中的所有方法的默認實現。我們常用到的適配器有:

  • ChannelHandlerAdapter

  • ChannelInboundAdapter

  • ChannelOutBoundAdapter

  • ChannelDuplexHandlerAdapter

 

重點學習三類:編碼器 解碼器 SimpleChannelHandler三類

Note: 異常如果不處理,會一直傳遞直到尾端被記錄是什麼意思?

2.4 eventLoopGroup eventLoop

簡單理解對應着jdk中的線程和線程池

 

3 problem

3.1 其他

3.1.1 池化

按需分配:ByteBufAllocator接口

爲了降低分配和釋放內存的開銷,Netty通過interface ByteBufAllocator實現了(ByteBuf的)池化,它可以用來分配我們所描述過的任何類型的ByteBuf實例。使用池化是特定於應用程序的決定,其並不會以任何方式改變ByteBuf API。

可以通過Channel(每個都可以有一個不同的ByteBufAllocator實例)或者綁定到ChannelHandler的ChannelHanlderContext獲取一個到ByteBufAllocator的引用。

io.netty.channel.Channel channel = ...; ByteBufAllocator allocator = channel.alloc(); ..... ChannelHandlerContext ctx = ...; ByteBufAllocator allocator1 = ctx.alloc(); ....

Netty提供了兩種ByteBufAllocator的實現:PooledByteAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的實例以提高性能並最大限度地減少內存碎片。此實現使用了一種稱爲jemalloc的已被大量現代操作系統所採用的高效方法來分配內存。後者的實現不池化ByteBuf實例,並且在每次它被調用時都會返回一個新的實例。

雖然Netty默認使用了PooledByteBufAllocator,但這可以很容易地通過ChannelConfig API或者在引導你的應用程序時指定一個不同的分配器來更改。

3.1.2 發送大文件

p.addLast("streamer", new ChunkedWriteHandler());

p.addLast("handler", new MyHandler());

一般在MyHandler中使用chunkedStream來完整文件的讀取,讀完之後使用writeAndFlush發送。

 

9 胡思亂想

現在想來,在以前寫安卓程序的,經常碰到這種遠程調用。在安卓程序中,因爲主線程是界面線程,該線程不能阻塞,否則界面就卡死了。因爲其他的數據請求很多都是異步調用。體現在代碼上就是從網絡層到知道最上層的Fragment層一堆回調。當時的網絡通信使用的是OkHttp框架或者volley之類,和Netty有某種程度的相似之處,但是還是有本質不同的。這些框架只是封裝出了一個易用的回調框架,解決了client的的大部分問題。

 

9.3 Netty與零拷貝

Zero-Copy-Capable Rich Byte Buffer 零拷貝的Buffer。爲什麼叫零拷貝?因爲在數據傳輸時,最終處理的數據會需要對單個傳輸層的報文,進行組合或者拆分。

???上面的這個解釋不是太理解,個人理解零拷貝是數據內容直接從內存到網絡協議棧。

 

NIO原生的ByteBuffer要做到這件事,需要對ByteBuffer內容進行拷貝,產生新的ByteBuffer,而Netty通過提供Composite(組合)和Slice(切分)兩種Buffer來實現零拷貝。這部分代碼在org.jboss.netty.buffer包中。

對於如何實現零拷貝,這種解釋也是不太明白。操作系統提供的功能決定了,和組合及切分的關係???

 

0 參考資料

1 [從I/O到netty] https://juejin.im/post/58bbaee6ac502e006b02f607

2 [深入淺出Netty] https://caorong.github.io/2016/12/24/head-first-netty-1/

3 [I/O網絡基礎] http://alicharles.com/article/netty-io-basic/

4 [阻塞和非阻塞] https://www.zhihu.com/question/19732473

5 [Netty常見問題] https://zhuanlan.zhihu.com/p/55007263

 

發佈了87 篇原創文章 · 獲贊 20 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章