導航:首頁 > 源碼編譯 > netty讀事件源碼

netty讀事件源碼

發布時間:2023-01-14 16:33:05

⑴ [Netty源碼分析]ByteBuf(一)

ByteBuf通過兩個指針協助讀寫操作,讀操作使用readerIndex,寫操作使用writerIndex.

readerIndex、writerIndex初始值是0,寫入數據時writerIndex增加,讀取數據時readerIndex增加,但是readerIndex不會超過writerIndex.

讀取之後,0-readerIndex之間的空間視為discard的,調用discardReadByte方法可以釋放這一部分空間,作用類似於ByteBuffer的compact方法.readerIndex-writerIndex之間的數據是可讀的,等價於ByteBuffer中position-limit之間的數據.

writerIndex-capacity之間的空間是可寫的,等價於ByteBuffer中limit-capacity之間的空間.

讀隻影響readerIndex、寫隻影響writerIndex,讀寫之間不需要調整指針位置,所以相較於NIO的ByteBuffer,可以極大的簡化讀寫操作

調用discardReadBytes會發生位元組數組的內存復制,所以頻繁調用會導致性能下降

ByteBuf對write操作進行了封裝,有ByteBuf的write操作負責進行剩餘咳喲好難過空間的校驗,如果可用緩沖區不足,ByteBuf會自動進行動態擴展。對於使用者而言不需要關心底層的校驗和擴展細節,只需要不超過capacity即可

對緩沖區進行讀操作時,有的時候我們需要對之前的操作進行回滾,讀操作並不會改變緩沖區的內容,回滾主要是重新設置索引信息

Mark:將當前的位置指針被分到mark變數中

Reset:恢復位置指針為mark中的變數值

ByteBuf有readerIndex、writerIndex,所以有四個相應的方法

markReaderIndex: 將當前readerIndex備份到markedReaderIndex中

resetReaderIndex: 將當前readerIndex設置為markedReaderIndex

markWriterIndex: 將當前readerIndex備份到markedWriterIndex中

resetWriterIndex: 將當前readerIndex設置為markedWriterIndex

3)slice:
返回當前ByteBuf的可讀子緩沖區,即從readerIndex到writerIndex的ByteBuf,返回的ByteBuf和原有緩沖區共享內容,但是維護獨立的索引.當修改其中一個ByteBuf的內容時,另一個也會改變,即雙方持有的是同一個對象的引用

常見類:

相比於PooledHeapByteBuf,UnpooledHeapByteBuf的實現更加簡單,也不容易出現內存管理的問題,所以才性能滿足的情況下,推薦使用UnpooledHeapByteBuf

在I/O通信線程的讀寫緩沖區中使用DirectByteBuf,後端業務消息的編碼使用HeapByteBuf,這樣的組合性能最優

⑵ Netty源碼筆記

Netty版本4.0.29.Final,以構造客戶端連接服務端的角度來追蹤源碼

NioEventLoopGroup的構造器中會調用父類MultithreadEventLoopGroup的構造器

在父類MultithreadEventExecutorGroup的構造器中

上面已經完成了NioEventLoop的創建,並保存在NioEventLoopGroup的數組屬性上。而關於NioEventLoop在具備線程池能力時,在何時啟動已經創建的線程呢?在SingleThreadEventExecutor::execute中可以找到答案

而關於NioEventLoop內線程啟動後的邏輯,可以在創建該線程時看到有向線程提交一個任務。根據任務內SingleThreadEventExecutor.this.run()可以定位到創建線程提交任務的NioEventLoop::run

NioEventLoop::run是Netty的核心所在。它是NioEventLoop的線程執行的唯一任務。方法內無限循環,阻塞等待IO事件或隊列中的任務

關於隊列中的任務從哪裡來?一是源碼內部,啟動時會直接提交任務到隊列中;二是可以直接取出channel中的NioEventLoop向其提交任務;三是使用Channel寫數據時,都是以任務的形式提交到隊列中。與Channel綁定的NioEventLoop循環時會消費提交到隊列中任務並執行,後續在分析unsafe時會一並提及。

客戶端Bootstrap配置引導時,需要指定Channel類型,後續會使用反射創建該Channel類型實例。

客戶端完成NioEventLoopGroup和Bootstrap的創建及對Bootstrap進行相關的設置後,使用Bootstrap嘗試與服務端建立連接。在同步與非同步之間,推薦使用非同步來監聽連接事件的結果。

AbstractBootstrap是Bootstrap的父類,initAndRegister中完成了通道的創建、初始化以及注冊

group()取到開始配置的NioEventLoopGroup,register在父類MultithreadEventLoopGroup中

雖說暫時不引入unsafe類邏輯,但在unsafe::register中,通道注冊完成後會調用管道的fireChannelRegistered方法,進而執行自定義的ChannelInitializer,最終形成完整的管道。
目前管道上鏈表有三個節點:head、自定義ChannelInitializer、tail(規定自頭向尾的流向為In,自尾向頭的為Out)
它們的類型都是AbstractChannelHandlerContext,每個Context上都持有對應的ChannelHandler

至此,Channel的初始化及注冊已經完成。回到Bootstrap::doConnect中,在Channel注冊這個非同步任務完成後,開始真正的連接服務端

channel連接建立完成後,可以使用channel寫數據

寫數據不會直接發送數據到服務端,而是會緩存起來,直到調用flush才會完成數據的最終發送。flush邏輯與寫數據一樣,channel到pipeline,tail到head。最終交由unsafe類來完成

以客戶端建立連接發送數據流程為例(服務端大部分邏輯相似),總結Netty的工作流程:

⑶ Netty核心技術及源碼剖析-Netty入站與出站機制

1、Netty的組件設計: Netty的主要組件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
2、ChannelHandler充當了處理入站和出站數據的應用程序邏輯的容器。例如,實現ChannelInboundHandler介面(或ChannelInboundHandlerAdapter),你就可以接收入站事件和數據,這些數據會被業務邏輯處理。當要給客戶端發送響應時,也可以從ChannelInboundHandler沖刷數據。業務邏輯通常寫在一個或者多個ChannelInboundHandler中。ChannelOutboundHandler原理一樣,只不過它是用來處理出站數據的。
3、ChannelPipeline提供了ChannelHandler鏈的容器。以客戶端應用程序為例,如果事件的運動方向是從客戶端到服務端的,那麼我們稱這些事件為出站的,即客戶端發送給伺服器端的數據會通過pipeline中的一些列ChannelOutboundHandler,並被這些Handler處理,反之則稱為入站的。

1、當Netty發送或者接受一個消息的時候,就將會發生一次數據轉換。入站消息會被解碼:從位元組轉換為另一種格式(比如java對象);如果是出站消息,它會被編碼成位元組。
2、Netty提供一些列實用的編解碼器,他們都實現了ChannelInboundHandler或者ChannelOutboundHandler介面。在這些類中,channelRead方法已經被重寫了。以入站為例,對於每個從入站Channel讀取的消息,這個方法會被調用。隨後,它將調用由解碼器所提供的decode()方法進行解碼,並將已經解碼的位元組轉發給ChannelPipeline中的下一個ChannelInboundHandler。

1、關系繼承圖

2、由於不可能知道遠程節點是否會一次性發送一個完整的信息,tcp有可能出現粘包拆包的問題,這個類會對入站數據進行緩沖,知道它准備好被處理。
3、一個關於ByteToMessageDecoder實例分析

⑷ netty源碼debug 解析

1.  EventLoopGroup bossGroup =newNioEventLoopGroup() 創建線程組

       看一下 EventExecutorGroup 介面 依賴關系

       它的主要的方法是next 方法

看一下 newNioEventLoopGroup 依賴關系

2 創建newNioEventLoopGroup 它的時候方法

2.1 重點看一下 SelectProvider.provider ,同時看一下類SelectProvider 這個類的作用

可參考  SelectProvider類解析

2.2

看一下 這個類  KQueueSelectorImpl的解析參考 的構造方法 KQueueSelectorImpl(SelectorProvider var1)

IOUtil.makePipe(false);是一個static native方法,所以我們沒辦法查看源碼。但是我們可以知道該函數返回了一個非堵塞的管道(pipe),底層是通過Linux的pipe系統調用實現的;創建了一個管道pipe並返回了一個64為的long型整數,該數的高32位存放了該管道讀端的文件描述符,低32位存放了該pipe的寫端的文件描述符。

3.完成Selector和Channel 綁定的在 Channel的 initAndRegister 

因為在group 中完成reactor線程模型的同事 注入了Selector 選擇器 group中的對象EventLoopGroup是包含Selector的 這就和我們的NIO模型

4 .看一下注冊事件 

5 下面看一下 AbstractCHannel的register方法

6.看到register0(promise) 很高興終於看到正真的注冊的方法了

但是還需要看doRegister();

7.看看doRegister 裡面的for循環做了什麼

重點說一下這個方法

將NioServerSocketChannel注冊到NioEventLoop的Selector上,this是對象NioServerSocketChannel 作為注冊的附件 attachment  這樣終於看到了 selector ,channel ,和attachment,NioEventLoop持有Selector對象在構造 reactor 線程模型的時候構造的,channel 是NioServerSocketChannel 是在初始化的時候構造的

8.接著上面的圖看看pipeline.fireChannelRegistered()做了什麼?

上圖中的initChannel((C) ctx,chanel)完成了我們實際的 pipeline的注入 並且移除我們默認的defaultChannPipeline 這個是在我們創建channel的時候默認的,用了這么久現在可以remove,感嘆設計的優秀呀。

到此完成了 selector  在reactor 模型中創建,channel 在 bind中創建 實例化,在上面看到了 Channel 和Selector的綁定,現在有看到了 pipeline的實例化。

9,pipeline的實例化 把所有的handel按照順序放入其中。

10.構造處理chain 鏈表結構

到此完成實例化。

10.現在我要找到run 方法

11.此處的execute其實是父類的方法執行,

12.我們看到了startExecution

13.接下來看看 executor.execute

14 調用的是SingleThreadEventExecutor.this.run 其實是調用了NioEventLoop的run

15 看到了selectNow 和select 等 接著又是runallTasks

16.看看runallTasks

17.再把 pollTak() 方法看一下

⑸ Netty源碼分析(七) PoolChunk

在分析源碼之前,我們先來了解一下Netty的內存管理機制。我們知道,jvm是自動管理內存的,這帶來了一些好處,在分配內存的時候可以方便管理,也帶來了一些問題。jvm每次分配內存的時候,都是先要去堆上申請內存空間進行分配,這就帶來了很大的性能上的開銷。當然,也可以使用堆外內存,Netty就用了堆外內存,但是內存的申請和釋放,依然需要性能的開銷。所以Netty實現了內存池來管理內存的申請和使用,提高了內存使用的效率。
PoolChunk就是Netty的內存管理的一種實現。Netty一次向系統申請16M的連續內存空間,這塊內存通過PoolChunk對象包裝,為了更細粒度的管理它,進一步的把這16M內存分成了2048個頁(pageSize=8k)。頁作為Netty內存管理的最基本的單位 ,所有的內存分配首先必須申請一塊空閑頁。Ps: 這里可能有一個疑問,如果申請1Byte的空間就分配一個頁是不是太浪費空間,在Netty中Page還會被細化用於專門處理小於4096Byte的空間申請 那麼這些Page需要通過某種數據結構跟演算法管理起來。
先來看看PoolChunk有哪些屬性

Netty採用完全二叉樹進行管理,樹中每個葉子節點表示一個Page,即樹高為12,中間節點表示頁節點的持有者。有了上面的數據結構,那麼頁的申請跟釋放就非常簡單了,只需要從根節點一路遍歷找到可用的節點即可。主要來看看PoolChunk是怎麼分配內存的。

Netty的內存按大小分為tiny,small,normal,而類型上可以分為PoolChunk,PoolSubpage,小於4096大小的內存就被分成PoolSubpage。Netty就是這樣實現了對內存的管理。
PoolChunk就分析到這里了。

⑹ Netty 源碼解析 ——— ChannelConfig 和 Attribute

嗯,本文與其說是ChannelConfig、Attribute源碼解析,不如說是對ChannelConfig以及Attribute結構層次的分析。因為這才是它們在Netty中使用到的重要之處。

在 Netty 源碼解析 ——— 服務端啟動流程 (下) 中說過,當我們在構建NioServerSocketChannel的時候同時會構建一個NioServerSocketChannelConfig對象賦值給NioServerSocketChannel的成員變數config。

而這一個NioServerSocketChannelConfig是當前NioServerSocketChannel配置屬性的集合。NioServerSocketChannelConfig主要用於對NioServerSocketChannel相關配置的設置(如,網路的相關參數配置),比如,配置Channel是否為非阻塞、配置連接超時時間等等。

NioServerSocketChannelConfig其實是一個ChannelConfig實例。ChannelConfig表示為一個Channel相關的配置屬性的集合。所以NioServerSocketChannelConfig就是針對於NioServerSocketChannel的配置屬性的集合。

ChannelConfig是Channel所需的公共配置屬性的集合,如,setAllocator(設置用於channel分配buffer的分配器)。而不同類型的網路傳輸對應的Channel有它們自己特有的配置,因此可以通過擴展ChannelConfig來補充特有的配置,如,ServerSocketChannelConfig是針對基於TCP連接的服務端ServerSocketChannel相關配置屬性的集合,它補充了針對TCP服務端所需的特有配置的設置setBacklog、setReuseAddress、setReceiveBufferSize。

DefaultChannelConfig作為ChannelConfig的默認實現,對ChannelConfig中的配置提供了默認值。

接下來,我們來看一個設置ChannelConfig的流程:
serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
我們可以在啟動服務端前通過ServerBootstrap來進行相關配置的設置,該選項配置會在Channel初始化時被獲取並設置到Channel中,最終會調用底層ServerSocket.setReuseAddress方法來完成配置的設置。
ServerBootstrap的init()方法:

首先對option和value進行校驗,其實就是進行非空校驗。
然後判斷對應的是哪個常量屬性,並進行相應屬性的設置。如果傳進來的ChannelOption不是已經設定好的常量屬性,則會列印一條警告級別的日誌,告知這是未知的channel option。
Netty提供ChannelOption的一個主要的功能就是讓特定的變數的值給類型化。因為從』ChannelOption<T> option』和』T value』可以看出,我們屬性的值類型T,是取決於ChannelOption的泛型的,也就屬性值類型是由屬性來決定的。

這里,我們可以看到有個ChannelOption類,它允許以類型安全的方式去配置一個ChannelConfig。支持哪一種ChannelOption取決於ChannelConfig的實際的實現並且也可能取決於它所屬的傳輸層的本質。

可見ChannelOption是一個Consant擴展類,Consant是Netty提供的一個單例類,它能安全去通過』==』來進行比較操作。通過ConstantPool進行管理和創建。
常量由一個id和name組成。id:表示分配給常量的唯一數字;name:表示常量的名字。

如上所說,Constant是由ConstantPool來進行管理和創建的,那麼ConstantPool又是個什麼樣的類了?

首先從constants中get這個name對應的常量,如果不存在則調用newConstant()來構建這個常量tempConstant,然後在調用constants.putIfAbsent方法來實現「如果該name沒有存在對應的常量,則插入,否則返回該name所對應的常量。(這整個的過程都是原子性的)」,因此我們是根據putIfAbsent方法的返回來判斷該name對應的常量是否已經存在於constants中的。如果返回為null,則說明當前創建的tempConstant就為name所對應的常量;否則,將putIfAbsent返回的name已經對應的常量值返回。(注意,因為ConcurrentHashMap不會允許value為null的情況,所以我們可以根據putIfAbsent返回為null則代表該name在此之前並未有對應的常量值)

正如我們前面所說的,這個ConstantPool<ChannelOption<Object>> pool(即,ChannelOption常量池)是ChannelOption的一個私有靜態成員屬性,用於管理和創建ChannelOption。

這些定義好的ChannelOption常量都已經存儲數到ChannelOption的常量池(ConstantPool)中了。

注意,ChannelOption本身並不維護選項值的信息,它只是維護選項名字本身。比如,「public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");」👈這只是維護了「SO_RCVBUF」這個選項名字的信息,同時泛型表示選擇值類型,即「SO_RCVBUF」選項值為Integer。

好了,到目前為止,我們對Netty的ChannelOption的設置以及底層的實現已經分析完了,簡單的來說:Netty在初始化Channel時會構建一個ChannelConfig對象,而ChannelConfig是Channel配置屬性的集合。比如,Netty在初始化NioServerSocketChannel的時候同時會構建一個NioServerSocketChannelConfig對象,並將其賦值給NioServerSocketChannel的成員變數config,而這個config(NioServerSocketChannelConfig)維護了NioServerSocketChannel的所有配置屬性。比如,NioServerSocketChannelConfig提供了setConnectTimeoutMillis方法來設置NioServerSocketChannel連接超時的時間。
同時,程序可以通過ServerBootstrap或Boostrap的option(ChannelOption<T> option, T value)方法來實現配置的設置。這里,我們通過ChannelOption來實現配置的設置,ChannelOption中已經將常用的配置項預定義為了常量供我們直接使用,同時ChannelOption的一個主要的功能就是讓特定的變數的值給類型化。因為從』ChannelOption<T> option』和』T value』可以看出,我們屬性的值類型T,是取決於ChannelOption的泛型的,也就屬性值類型是由屬性來決定的。

一個attribute允許存儲一個值的引用。它可以被自動的更新並且是線程安全的。
其實Attribute就是一個屬性對象,這個屬性的名稱為AttributeKey<T> key,而屬性的值為T value。

我們可以通過程序ServerBootstrap或Boostrap的attr方法來設置一個Channel的屬性,如:
serverBootstrap.attr(AttributeKey.valueOf("userID"), UUID.randomUUID().toString());
當Netty底層初始化Channel的時候,就會將我們設置的attribute給設置到Channel中:

如上面所說,Attribute就是一個屬性對象,這個屬性的名稱為AttributeKey<T> key,而屬性的值為T value。
而AttributeKey也是Constant的一個擴展,因此也有一個ConstantPool來管理和創建,這和ChannelOption是類似的。

Channel類本身繼承了AttributeMap類,而AttributeMap它持有多個Attribute,這些Attribute可以通過AttributeKey來訪問的。所以,才可以通過channel.attr(key).set(value)的方式將屬性設置到channel中了(即,這里的attr方法實際上是AttributeMap介面中的方法)。

AttributeKey、Attribute、AttributeMap間的關系:
AttributeMap相對於一個map,AttributeKey相當於map的key,Attribute是一個持有key(AttributeKey)和value的對象。因此在map中我們可以通過AttributeKey key獲取Attribute,從而獲取Attribute中的value(即,屬性值)。

Q:ChannelHandlerContext和Channel都提供了attr方法,那麼它們設置的屬性作用域有什麼不同了?
A:在Netty 4.1版本之前,它們兩設置的屬性作用域確實存在著不同,但從Netty 4.1版本開始,它們兩設置的屬性的作用域已經完全相同了。

若文章有任何錯誤,望大家不吝指教:)

聖思園《精通並發與Netty》

⑺ Netty源碼_NioEventLoop詳解

這一章我們將講解 netty 中真正使用的事件輪詢器 NioEventLoop ,通過這一章,你將了解:

netty 的事件輪詢器也是通過的 java nio 的選擇器 Selector 來管理多個嵌套字 Socket 的通道 channel 。
那麼選擇器 Selector 是如何與通道 channel ,並管理它們的呢。

在 SelectableChannel 類用 register 方法將通道注冊到選擇器 Selector 中。

如果當前通道已經注冊到給定的選擇器 sel 上了,調用這個方法,更改了這個通道關注的 IO 事件類型 ops ,和綁定的對象 att 。
在 AbstractSelectableChannel 實現中我們看到:

選擇鍵 SelectionKey 的功能並不復雜,主要是和通道 channel 的 IO 事件有關,分為四種:

選擇器的主要功能就是獲取已經准備好的通道 channel 。

一共有三個方法 int selectNow() , int select(long timeout) 和 int select() 。

Set<SelectionKey> selectedKeys() 方法返回准備好的選擇鍵集合,通過選擇鍵就可以得到對應的通道 channel 。

Selector wakeup() 可以喚醒被 select() 和 select(long timeout) 阻塞的等待線程。

NioEventLoop 的成員變數中有兩個選擇器實例 unwrappedSelector 和 selector

事件輪詢器如何實現事件輪詢的,就是主要看它的 run 方法實現:

我們知道事件輪詢器要處理兩種事件:通道的 IO 事件 和 任務(包括計劃任務和待執行任務),那麼就要合理分配時間:

NioEventLoop 基本邏輯已經說清楚了,我們知道它是如何平衡處理 IO 事件和 待執行的任務的。

⑻ Netty源碼_NioEventLoopGroup詳解

上一章 介紹 NioEventLoop 的實現原理,但是我們在 netty 一般都是直接使用 NioEventLoopGroup 類,直接創建一個事件輪詢器組。

這個是事件輪詢器組 EventExecutorGroup 實現的抽象基類。

這個 MultithreadEventExecutorGroup 類其實為事件輪詢器組 EventExecutorGroup 奠定了基礎啊。
在它的構造方法中,我們發現:

這個類沒有啥可講解的。

你會發現事件輪詢器組 EventExecutorGroup 的實現非常簡單,就是管理子的事件輪詢器 EventLoop 。

閱讀全文

與netty讀事件源碼相關的資料

熱點內容
手機怎麼把兩張圖片做成文件夾 瀏覽:720
抖音導出表格發貨加密 瀏覽:132
自己電腦怎麼模擬成伺服器 瀏覽:552
單片機的Vpp是 瀏覽:350
iua編譯器下載官方 瀏覽:84
壓縮機高低壓快速平衡 瀏覽:873
phpai 瀏覽:707
怎麼不被命令 瀏覽:86
大話緣定三生伺服器什麼便宜 瀏覽:967
idea編譯內部類 瀏覽:466
pdf2word在線轉換 瀏覽:588
tim儲存在哪個文件夾 瀏覽:621
華碩電腦u盤加密最簡單方法 瀏覽:853
編程過路馬游戲 瀏覽:608
python多重線性回歸 瀏覽:516
冰箱壓縮機工作圖 瀏覽:843
OTC機器人編程師 瀏覽:948
簽名的apk能防止反編譯嗎 瀏覽:311
我的世界伺服器如何版本 瀏覽:285
集合競價買入額指標源碼 瀏覽:114