Java併發編程學習-日記6、Netty基礎知識點

Netty的服務啓動類ServerBootstrap:Bootstrap類是Netty提供的一個便利的工廠類,可以通過它來完成Netty的客戶端或服務器端的Netty組件的組裝,以及Netty程序的初始化。它的職責是一個組裝和集成器,將不同的Netty組件組裝在一起。另外,ServerBootstrap能夠按照應用場景的需要,爲組件設置好對應的參數,最後實現Netty服務器的監聽和啓動。Netty中的各種組件:服務器啓動器、緩衝區、反應器、Handler業務處理器、Future異步任務監聽、數據傳輸通道。

1、Channel通道組件

Netty中不直接使用Java NIO的Channel通道組件,對Channel通道組件進行了自己的封裝。在Netty中,有一系列的Channel通道組件,爲了支持多種通信協議,對於每一種通信連接協議,Netty都實現了自己的通道。除了Java的NIO,Netty還能處理Java的面向流的OIO(Old-IO,即傳統的阻塞式IO)。Netty中的每一種協議的通道,都有NIO(異步IO)和OIO(阻塞式IO)兩個。Netty中常見的通道類型如下:

  • NioSocketChannel:異步非阻塞TCP Socket傳輸通道。
  • OioSocketChannel:同步阻塞式TCP Socket傳輸通道。
  • NioServerSocketChannel:異步非阻塞TCP Socket服務器端監聽通道。在Netty的NioSocketChannel內部封裝了一個Java NIO的SelectableChannel成員。通過這個內部的Java NIO通道,Netty的NioSocketChannel通道上的IO操作,最終會落地到Java NIO的SelectableChannel底層通道。
  • OioServerSocketChannel:同步阻塞式TCP Socket服務器端監聽通道。
  • NioDatagramChannel:異步非阻塞的UDP傳輸通道。
  • OioDatagramChannel:同步阻塞式的UDP傳輸通道。
  • NioSctpChannel:異步非阻塞Sctp傳輸通道。
  • OioSctpChannel:同步阻塞式Sctp傳輸通道。
  • NioSctpServerChannel:異步非阻塞Sctp服務器端監聽通道。
  • OioSctpServerChannel:同步阻塞式Sctp服務器端監聽通道。

2、Netty中的反應器

Netty中的反應器有多個實現類,與Channel通道類有關係。對應於NioSocketChannel通道,Netty的反應器類爲:NioEventLoop。 NioEventLoop類綁定了兩個重要的Java成員屬性:一個是Thread線程類的成員,一個是Java NIO選擇器的成員屬性。一個NioEventLoop擁有一個Thread線程,負責一個Java NIO Selector選擇器的IO事件輪詢。理論上來說,一個EventLoopNetty反應器和NettyChannel通道是一對多的關係:一個反應器可以註冊成千上萬的通道。多個EventLoop線程組成一個EventLoopGroup線程。

3、Netty的Handler處理器

Netty的Handler處理器分爲兩大類:第一類是ChannelInboundHandler通道入站處理器(入站指的是輸入,對於處理入站的IO事件的方法,ChannelInboundHandlerAdapter則是Netty提供的入站處理的默認實現);第二類是ChannelOutboundHandler通道出站處理器(出站指的是輸出)。

  • Netty中的入站處理,觸發的方向爲:從通道到ChannelInboundHandler通道入站處理器。
  • Netty中的出站處理指的是從ChanneOutboundHandler通道出站處理器到通道的某次IO操作,例如,在應用程序完成業務處理後,可以通過ChanneOutboundHandler通道出站處理器將處理的結果寫入底層通道。它的最常用的一個方法就是write()方法,把數據寫入到通道。

(1)反應器(或者SubReactor子反應器)和通道之間是一對多的(2)通道和Handler處理器實例之間,是多對多的。

ChannelInitializer處理器有一個泛型參數SocketChannel,它代表需要初始化的通道類型,這個類型需要和前面的啓動器中設置的通道類型,一一對應。

操作系統底層的socket描述符分爲兩類:

  • 連接監聽類型。連接監聽類型的socket描述符,放在服務器端,它負責接收客戶端的套接字連接;在服務器端,一個“連接監聽類型”的socket描述符可以接受(Accept)成千上萬的傳輸類的socket描述符。
  • 傳輸數據類型。數據傳輸類的socket描述符負責傳輸數據。同一條TCP的Socket傳輸鏈路,在服務器和客戶端,都分別會有一個與之相對應的數據傳輸類型的socket描述符。

在Netty中,將有接收關係的NioServerSocketChannel和NioSocketChannel,叫作父子通道。其中,NioServerSocketChannel負責服務器連接監聽和接收,也叫父通道(Parent Channel)。對應於每一個接收到的NioSocketChannel傳輸類通道,也叫子通道(Child Channel)。

ChannelOption通道選項:

  • 1)SO_RCVBUF,SO_SNDBUF 此爲TCP參數。每個TCP socket(套接字)在內核中都有一個發送緩衝區和一個接收緩衝區,這兩個選項就是用來設置TCP連接的這兩個緩衝區大小的。TCP的全雙工的工作模式以及TCP的滑動窗口便是依賴於這兩個獨立的緩衝區及其填充的狀態。
  • 2)TCP_NODELAY 此爲TCP參數。表示立即發送數據,默認值爲True(Netty默認爲True,而操作系統默認爲False)。該值用於設置Nagle算法的啓用,該算法將小的碎片數據連接成更大的報文(或數據包)來最小化所發送報文的數量,如果需要發送一些較小的報文,則需要禁用該算法。

這個參數的值,與是否開啓Nagle算法是相反的,設置爲true表示關閉,設置爲false表示開啓。

  • 3)SO_KEEPALIVE 此爲TCP參數。表示底層TCP協議的心跳機制。true爲連接保持心跳,默認值爲false。啓用該功能時,TCP會主動探測空閒連接的有效性。可以將此功能視爲TCP的心跳機制,需要注意的是:默認的心跳間隔是7200s即2小時。Netty默認關閉該功能。
  • 4)SO_REUSEADDR 此爲TCP參數。設置爲true時表示地址複用,默認值爲false。
  • 5)SO_LINGER 此爲TCP參數。表示關閉socket的延遲時間,默認值爲-1,表示禁用該功能。-1表示socket.close()方法立即返回,但操作系統底層會將發送緩衝區全部發送到對端。0表示socket.close()方法立即返回,操作系統放棄發送緩衝區的數據,直接向對端發送RST包,對端收到復位錯誤。非0整數值表示調用socket.close()方法的線程被阻塞,直到延遲時間到來、發送緩衝區中的數據發送完畢,若超時,則對端會收到復位錯誤。
  • 6)SO_BACKLOG 此爲TCP參數。表示服務器端接收連接的隊列長度,如果隊列已滿,客戶端連接將被拒絕。默認值,在Windows中爲200,其他操作系統爲128。
  • 7)SO_BROADCAST 此爲TCP參數。表示設置廣播模式。

Netty在對通道進行初始化的時候,將pipeline屬性初始化爲DefaultChannelPipeline的實例,每個通道擁有一條ChannelPipeline處理器流水線。

通道接口的幾個重要方法:

  • 方法1、ChannelFuture connect(SocketAddress address) 此方法的作用爲:連接遠程服務器。方法的參數爲遠程服務器的地址,調用後會立即返回,返回值爲負責連接操作的異步任務ChannelFuture。此方法在客戶端的傳輸通道使用。
  • 方法2、ChannelFuture bind(SocketAddress address) 此方法的作用爲:綁定監聽地址,開始監聽新的客戶端連接。此方法在服務器的新連接監聽和接收通道使用。
  • 方法3、ChannelFuture close() 此方法的作用爲:關閉通道連接,返回連接關閉的ChannelFuture異步任務。如果需要在連接正式關閉後執行其他操作,則需要爲異步任務設置回調方法;或者調用ChannelFuture異步任務的sync( )方法來阻塞當前線程,一直等到通道關閉的異步任務執行完畢。
  • 方法4、Channel read() 此方法的作用爲:讀取通道數據,並且啓動入站處理。具體來說,從內部的Java NIO Channel通道讀取數據,然後啓動內部的Pipeline流水線,開啓數據讀取的入站處理。此方法的返回通道自身用於鏈式調用。
  • 方法5、ChannelFuture write(Object o) 此方法的作用爲:向通道寫數據,啓程出站流水處理,把處理後的最終數據寫到底層Java NIO通道。此方法的返回值爲出站處理的異步處理任務。
  • 方法6、Channel flush() 此方法的作用爲:將緩衝區中的數據立即寫出到對端。並不是每一次write操作都是將數據直接寫出到對端,write操作的作用在大部分情況下僅僅是寫入到操作系統的緩衝區,操作系統會將根據緩衝區的情況,決定什麼時候把數據寫到對端。而執行flush()方法立即將緩衝區的數據寫到對端。

EmbeddedChannel

EmbeddedChannel僅僅是模擬入站與出站的操作,底層不進行實際的傳輸,不需要啓動Netty服務器和客戶端。而且Embedded-Channel的其他的事件機制和處理流程和真正的傳輸通道是一模一樣。EmbeddedChannel單元測試的輔助方法中最爲重要的兩個方法爲:writeInbound和readOutbound方法。

整個的IO處理操作環節包括:從通道讀數據包、數據包解碼、業務處理、目標數據編碼、把數據包寫到通道,然後由通道發送到對端。用戶程序主要在Handler業務處理器中,Handler涉及的環節爲:數據包解碼、業務處理、目標數據編碼、把數據包寫到通道中。

  • 入站處理,觸發的方向爲:自底向上,Netty的內部(如通道)到ChannelInboundHandler入站處理器。數據包解碼、業務處理兩個環節——屬於入站處理器的工作。
  • 出站處理,觸發的方向爲:自頂向下,從ChannelOutboundHandler出站處理器到Netty的內部(如通道)。目標數據編碼、把數據包寫到通道中兩個環節——屬於出站處理器的工作。

當數據或者信息入站到Netty通道時,Netty將觸發入站處理器ChannelInboundHandler所對應的入站API,進行入站操作操作。

  • 1)channelRegistered 當通道註冊完成後,Netty會調用fireChannelRegistered,觸發通道註冊事件。通道會啓動該入站操作的流水線處理,在通道註冊過的入站處理器Handler的channelRegistered方法,會被調用到。
  • 2)channelActive 當通道激活完成後,Netty會調用fireChannelActive,觸發通道激活事件。通道會啓動該入站操作的流水線處理,在通道註冊過的入站處理器Handler的channelActive方法,會被調用到。
  • 3)channelRead 當通道緩衝區可讀,Netty會調用fireChannelRead,觸發通道可讀事件。通道會啓動該入站操作的流水線處理,在通道註冊過的入站處理器Handler的channelRead方法,會被調用到。
  • 4)channelReadComplete 當通道緩衝區讀完,Netty會調用fireChannelReadComplete,觸發通道讀完事件。通道會啓動該入站操作的流水線處理,在通道註冊過的入站處理器Handler的channelReadComplete方法,會被調用到。
  • 5)channelInactive 當連接被斷開或者不可用,Netty會調用fireChannelInactive,觸發連接不可用事件。通道會啓動對應的流水線處理,在通道註冊過的入站處理器Handler的channelInactive方法,會被調用到。
  • 6)exceptionCaught 當通道處理過程發生異常時,Netty會調用fireExceptionCaught,觸發異常捕獲事件。通道會啓動異常捕獲的流水線處理,在通道註冊過的處理器Handler的exceptionCaught方法,會被調用到。注意,這個方法是在通道處理器中ChannelHandler定義的方法,入站處理器、出站處理器接口都繼承到了該方法。

在實際開發中,只需要繼承這個ChannelInboundHandlerAdapter默認實現,重寫自己需要的方法即可。

當業務處理完成後,需要操作Java NIO底層通道時,通過一系列的ChannelOutboundHandler通道出站處理器,完成Netty通道到底層通道的操作。比方說建立底層連接、斷開底層連接、寫入底層Java NIO通道等。

  • 1)bind 監聽地址(IP+端口)綁定:完成底層Java IO通道的IP地址綁定。如果使用TCP傳輸協議,這個方法用於服務器端。
  • 2)connect 連接服務端:完成底層Java IO通道的服務器端的連接操作。如果使用TCP傳輸協議,這個方法用於客戶端。
  • 3)write 寫數據到底層:完成Netty通道向底層Java IO通道的數據寫入操作。此方法僅僅是觸發一下操作而已,並不是完成實際的數據寫入操作。
  • 4)flush 騰空緩衝區中的數據,把這些數據寫到對端:將底層緩存區的數據騰空,立即寫出到對端。
  • 5)read 從底層讀數據:完成Netty通道從Java IO通道的數據讀取。
  • 6)disConnect 斷開服務器連接:斷開底層Java IO通道的服務器端連接。如果使用TCP傳輸協議,此方法主要用於客戶端。
  • 7)close 主動關閉通道:關閉底層的通道,例如服務器端的新連接監聽通道。

在Netty中,它的默認實現爲ChannelOutboundHandlerAdapter,在實際開發中,只需要繼承這個ChannelOutboundHandlerAdapter默認實現,重寫自己需要的方法即可。

ChannelPipeline(通道流水線)

一條Netty通道需要很多的Handler業務處理器來處理業務。Netty設計了一個特殊的組件,叫作ChannelPipeline(通道流水線),它像一條管道,將綁定到一個通道的多個Handler處理器實例,串在一起,形成一條流水線。Netty的業務處理器流水線ChannelPipeline是基於責任鏈設計模式(Chain of Responsibility)來設計的,ChannelPipeline(通道流水線)的默認實現,實際上被設計成一個雙向鏈表,能夠支持動態地添加和刪除Handler業務處理器。所有的Handler處理器實例被包裝成了雙向鏈表的節點,被加入到了ChannelPipeline(通道流水線)中。Netty是這樣規定的:入站處理器Handler的執行次序,是從前到後;出站處理器Handler的執行次序,是從後到前。

  • 每一個通道的子通道,都用一條ChannelPipeline流水線。它的內部有一個雙向的鏈表。裝配流水線的方式是:將業務處理器ChannelHandler實例加入雙向鏈中。
  • 裝配子通道的Handler流水線調用childHandler()方法,傳遞一個ChannelInitializer通道初始化類的實例。在父通道成功接收一個連接,並創建成功一個子通道後,就會初始化子通道。

爲什麼不需要裝配父通道的流水線呢?

原因是:父通道也就是NioServerSocketChannel連接接受通道,它的內部業務處理是固定的:接受新連接後,創建子通道,然後初始化子通道,所以不需要特別的配置。如果需要完成特殊的業務處理,可以使用ServerBootstrap的handler(ChannelHandler handler)方法,爲父通道設置ChannelInitializer初始化器。

在Handler業務處理器被添加到流水線中時,會創建一個通道處理器上下文ChannelHandlerContext,它代表了ChannelHandler通道處理器和ChannelPipeline通道流水線之間的關聯。ChannelHandlerContext中包含了有許多方法,主要可以分爲兩類:第一類是獲取上下文所關聯的Netty組件實例,如所關聯的通道、所關聯的流水線、上下文內部Handler業務處理器實例等;第二類是入站和出站處理方法。

在Channel、ChannelPipeline、ChannelHandlerContext三個類中,會有同樣的出站和入站處理方法,如果通過Channel或Channel-Pipeline 的實例來調用這些方法,它們就會在整條流水線中傳播。然而,如果是通過ChannelHandlerContext通道處理器上下文進行調用,就只會從當前的節點開始執行Handler業務處理器,並傳播到同類型處理器的下一站(節點)。

Channel、Handler、ChannelHandlerContext三者的關係爲:Channel通道擁有一條ChannelPipeline通道流水線,每一個流水線節點爲一個ChannelHandlerContext通道處理器上下文對象,每一個上下文中包裹了一個ChannelHandler通道處理器。在ChannelHandler通道處理器的入站/出站處理方法中,Netty都會傳遞一個Context上下文實例作爲實際參數。通過Context實例的實參,在業務處理中,可以獲取ChannelPipeline通道流水線的實例或者Channel通道的實例。

如何截斷入站處理流程?

在channelRead方法中,不再調用父類的channelRead入站方法。在channelRead方法中,入站處理傳入下一站還有一種方法:調用Context上下文的ctx.fireChannelRead(msg)方法。如果要截斷流水線的處理,很顯然,就不能調用ctx.fireChannelRead(msg)方法。如果要截斷其他的入站處理的流水線操作(使用Xxx指代),也可以同樣處理: (1)不調用supper.channelXxx (ChannelHandler-Context …)  (2)也不調用ctx.fireChannelXxx()。

出站處理流程只要開始執行,就不能被截斷。】強行截斷的話,Netty會拋出異常。如果業務條件不滿足,可以不啓動出站處理。

4、Netty buffer:

與Java NIO的ByteBuffer相比,ByteBuf的優勢如下:

  • Pooling(池化,這點減少了內存複製和GC,提升了效率)
  • 複合緩衝區類型,支持零複製
  • 不需要調用flip()方法去切換讀/寫模式
  • 擴展性好,例如StringBuffer ·可以自定義緩衝區類型
  • 讀取和寫入索引分開
  • 方法的鏈式調用
  • 可以進行引用計數,方便重複使用

ByteBuf通過三個整型的屬性有效地區分可讀數據和可寫數據,使得讀寫之間相互沒有衝突。這三個屬性定義在AbstractByteBuf抽象類中,分別是: ·readerIndex(讀指針) ·writerIndex(寫指針) ·maxCapacity(最大容量)Netty的ByteBuf的內存回收工作是通過【引用計數】的方式管理的。JVM中使用“計數器”(一種GC算法)來標記對象是否“不可達”進而收回(注:GC是Garbage Collection的縮寫,即Java中的垃圾回收機制),Netty也使用了這種手段來對ByteBuf的引用進行計數。Netty採用“計數器”來追蹤ByteBuf的生命週期,一是對Pooled ByteBuf的支持,二是能夠儘快地“發現”那些可以回收的ByteBuf(非Pooled),以便提升ByteBuf的分配和銷燬的效率。

默認情況下,當創建完一個ByteBuf時,它的引用爲1;每次調用retain()方法,它的引用就加1;每次調用release()方法,就是將引用計數減1;如果引用爲0,再次訪問這個ByteBuf對象,將會拋出異常;如果引用爲0,表示這個ByteBuf沒有哪個進程引用它,它佔用的內存需要回收。在Netty中,引用計數爲0的緩衝區不能再繼續使用。爲了確保引用計數不會混亂,在Netty的業務處理器開發過程中,應該堅持一個原則:retain和release方法應該結對使用。簡單地說,在一個方法中,調用了retain,就應該調用一次release。

如果retain和release這兩個方法,一次都不調用呢?

在緩衝區使用完成後,調用一次release,就是釋放一次。例如在Netty流水線上,中間所有的Handler業務處理器處理完ByteBuf之後直接傳遞給下一個,由最後一個Handler負責調用release來釋放緩衝區的內存空間。

當引用計數已經爲0,Netty會進行ByteBuf的回收。分爲兩種情況:

  • Pooled池化的ByteBuf內存,回收方法是:放入可以重新分配的ByteBuf池子,等待下一次分配。
  • Unpooled未池化的ByteBuf緩衝區,回收分爲兩種情況:
  1. 如果是堆(Heap)結構緩衝,會被JVM的垃圾回收機制回收;
  2. 如果是Direct類型,調用本地方法釋放外部內存(unsafe.freeMemory)

Netty提供了ByteBufAllocator的兩種實現:PoolByteBufAllocator和UnpooledByteBufAllocator。

  • PoolByteBufAllocator(池化ByteBuf分配器)將ByteBuf實例放入池中,提高了性能,將內存碎片減少到最小;這個池化分配器採用了jemalloc高效內存分配的策略。
  • UnpooledByteBufAllocator是普通的未池化ByteBuf分配器,它沒有把ByteBuf放入池中,每次被調用時,返回一個新的ByteBuf實例;通過Java的垃圾回收機制回收。
  • 在Netty中,默認的分配器爲ByteBufAllocator.DEFAULT,可以通過Java系統參數(System Property)的選項io.netty.allocator.type進行配置,配置時使用字符串值:"unpooled","pooled"。

ByteBuffer緩衝區類型:根據內存的管理方不同,分爲堆緩存區和直接緩存區,也就是Heap ByteBuf和Direct ByteBuf。

Direct Memory特殊說明:

  • Direct Memory不屬於Java堆內存,所分配的內存其實是調用操作系統malloc()函數來獲得的;由Netty的本地內存堆Native堆進行管理。
  • Direct Memory容量可通過-XX:MaxDirectMemorySize來指定,如果不指定,則默認與Java堆的最大值(-Xmx指定)。
  • Direct Memory的使用避免了Java堆和Native堆之間來回複製。
  • 在需要頻繁創建緩衝區的場合,由於創建和銷燬Direct Buffer(直接緩衝區)的代價比較高昂,因此不宜使用Direct Buffer。
  • 對Direct Buffer的讀寫比Heap Buffer快,但是它的創建和銷燬比普通Heap Buffer慢。
  • 在Java的垃圾回收機制回收Java堆時,Netty框架也會釋放不再使用的Direct Buffer緩衝區,因爲它的內存爲堆外內存,所以清理的工作不會爲Java虛擬機(JVM)帶來壓力。
  • 垃圾回收的應用場景:

(1)垃圾回收僅在Java堆被填滿,以至於無法爲新的堆分配請求提供服務時發生;

(2)在Java應用程序中調用System.gc()函數來釋放內存。

Heap ByteBuf和Direct ByteBuf兩類緩衝區的使用。它們有以下幾點不同:

  • 創建的方法不同:Heap ByteBuf通過調用分配器的buffer()方法來創建;而Direct ByteBuf的創建,是通過調用分配器的directBuffer()方法。
  • Heap ByteBuf緩衝區可以直接通過array()方法讀取內部數組;而Direct ByteBuf緩衝區不能讀取內部數組。
  • 可以調用hasArray()方法來判斷是否爲Heap ByteBuf類型的緩衝區;如果hasArray()返回值爲true,則表示是Heap堆緩衝,否則就不是。
  • Direct ByteBuf要讀取緩衝數據進行業務處理,相對比較麻煩,需要通過getBytes/readBytes等方法先將數據複製到Java的堆內存,然後進行其他的計算。

Netty的Reactor反應器線程會在底層的Java NIO通道讀數據時,也就是AbstractNioByteChannel.NioByteUnsafe.read()處,調用ByteBufAllocator方法,創建ByteBuf實例,從操作系統緩衝區把數據讀取到Bytebuf實例中,然後調用pipeline.fireChannelRead(byteBuf)方法將讀取到的數據包送入到入站處理流水線中。

入站ByteBuf自動釋放的方法:

  • TailHandler自動釋放

Netty默認會在ChannelPipline通道流水線的最後添加一個TailHandler末尾處理器,它實現了默認的處理方法,在這些方法中會幫助完成ByteBuf內存釋放的工作。如果自定義的InboundHandler入站處理器繼承自ChannelInboundHandlerAdapter適配器,那麼可以調用以下兩種方法來釋放ByteBuf內存:

(1)手動釋放ByteBuf。具體的方式爲調用byteBuf.release()。

(2)調用父類的入站方法將msg向後傳遞,依賴後面的處理器釋放ByteBuf。具體的方式爲調用基類的入站處理方法super.channelRead(ctx,msg)。

  • SimpleChannelInboundHandler自動釋放

如果Handler業務處理器需要截斷流水線的處理流程,不將ByteBuf數據包送入後邊的InboundHandler入站處理器,這時,流水線末端的TailHandler末尾處理器自動釋放緩衝區的工作自然就失效了。 在這種場景下,Handler業務處理器有兩種選擇:

(1)手動釋放ByteBuf實例。

(2)繼承SimpleChannelInboundHandler,利用它的自動釋放功能。

ByteBuf的淺層複製分爲兩種,有切片(slice)淺層複製和整體(duplicate)淺層複製。

1、切片(slice)淺層複製

  • ByteBuf的slice方法可以獲取到一個ByteBuf的一個切片。一個ByteBuf可以進行多次的切片淺層複製;多次切片後的ByteBuf對象可以共享一個存儲區域。
  • 調用slice()方法後,返回的切片是一個新的ByteBuf對象,該對象的幾個重要屬性值,大致如下:
    1. ·readerIndex(讀指針)的值爲0。
    2. ·writerIndex(寫指針)的值爲源Bytebuf的readableBytes()可讀字節數。
    3. ·maxCapacity(最大容量)的值爲源Bytebuf的readableBytes( )可讀字節數。
  • 切片後的新Bytebuf有兩個特點:
    1. ·切片不可以寫入,原因是:maxCapacity與writerIndex值相同。
    2. ·切片和源ByteBuf的可讀字節數相同,原因是:切片後的可讀字節數爲自己的屬性writerIndex – readerIndex,也就是源ByteBuf的readableBytes() - 0。
  • 切片後的新ByteBuf和源ByteBuf的關聯性:
    1. ·切片不會複製源ByteBuf的底層數據,底層數組和源ByteBuf的底層數組是同一個。
    2. ·切片不會改變源ByteBuf的引用計數。

2、整體(duplicate)淺層複製:

  • duplicate()返回的是源ByteBuf的整個對象的一個淺層複製,包括如下內容:
  1. ·duplicate的讀寫指針、最大容量值,與源ByteBuf的讀寫指針相同。
  2. ·duplicate()不會改變源ByteBuf的引用計數。
  3. ·duplicate()不會複製源ByteBuf的底層數據。

淺層複製方法不會實際去複製數據,也不會改變ByteBuf的引用計數,這就會導致一個問題:在源ByteBuf調用release()之後,一旦引用計數爲零,就變得不能訪問了;在這種場景下,源ByteBuf的所有淺層複製實例也不能進行讀寫了;如果強行對淺層複製實例進行讀寫,則會報錯。 因此,在調用淺層複製實例時,可以通過調用一次retain()方法來增加引用,表示它們對應的底層內存多了一次引用,引用計數爲2。在淺層複製實例用完後,需要調用兩次release()方法,將引用計數減一,這樣就不影響源ByteBuf的內存釋放。

在Netty中,無論是出站操作,還是出站操作,都有兩大的特點:

  • (1)同一條通道的所有出/入站處理都是串行的,而不是並行的。換句話說,同一條通道上的所有出/入站處理都會在它所綁定的EventLoop線程上執行。既然只有一個線程負責,那就只有串行的可能。EventLoop線程的任務隊列是一個MPSC隊列(即多生產者單消費者隊列)只有EventLoop線程自己是唯一的消費者,它將遍歷任務隊列,逐個執行任務;其他線程只能作爲生產者,它們的出/入站操作都會作爲異步任務加入到任務隊列。通過MPSC隊列,確保了EventLoop線程能做到:同一個通道上所有的IO操作是串行的,不是並行的。這樣,不同的Handler業務處理器之間不需要進行線程的同步,這點也能大大提升IO的性能。
  • (2)Netty的一個出/入站操作不是一次的單一Handler業務處理器操作,而是流水線上的一系列的出/入站處理流程。只有整個流程都處理完,出/入站操作才真正處理完成。

5、NETTY Future

Netty繼承和擴展了JDK Future系列異步回調的API,定義了自身的Future系列接口和類,實現了異步任務的監控、異步執行結果的獲取。Netty對JavaFuture異步任務的擴展如下:

(1)繼承Java的Future接口,得到了一個新的屬於Netty自己的Future異步任務接口;該接口對原有的接口進行了增強,使得Netty異步任務能夠以非阻塞的方式處理回調的結果。

(2)引入了一個新接口——GenericFutureListener,用於表示異步執行完成的監聽器。這個接口和Guava的FutureCallbak回調接口不同。Netty使用了監聽器的模式,異步任務的執行完成後的回調邏輯抽象成了Listener監聽器接口。可以將Netty的GenericFutureListener監聽器接口加入Netty異步任務Future中,實現對異步任務執行狀態的事件監聽。

GenericFutureListener擁有一個回調方法:operationComplete,表示異步任務操作完成。在Future異步任務執行完成後,將回調此方法。在大多數情況下,Netty的異步回調的代碼編寫在GenericFutureListener接口的實現類中的operationComplete方法中。GenericFutureListener的父接口EventListener是一個空接口,沒有任何的抽象方法,是一個僅僅具有標識作用的;GenericFutureListener接口在Netty中是一個基礎類型接口。在網絡編程的異步回調中,一般使用Netty中提供的某個子接口,如ChannelFutureListener。

Netty的Future接口一般不會直接使用,而是會使用子接口。Netty有一系列的子接口,代表不同類型的異步任務,如ChannelFuture接口。在Netty的網絡編程中,網絡連接通道的輸入和輸出處理都是異步進行的,都會返回一個ChannelFuture接口的實例。

如何判斷writeAndFlush()執行完畢?

writeAndFlush()方法會返回一個ChannelFuture異步任務實例,通過爲ChannelFuture異步任務增加GenericFutureListener監聽器的方式來判斷writeAndFlush()是否已經執行完畢。當GenericFutureListener監聽器的operationComplete方法被回調時,表示writeAndFlush()方法已經執行完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章