略過導覽

4.0 新功能與注意事項

您知道這個頁面是自動從 一個 Github Wiki 頁面產生 的嗎?您可以在 這裡 自行修改!

本文件將引導您瀏覽 Netty 主要版本的重大變更和新功能清單,讓您了解如何將應用程式移植到新版本。

專案結構變更

我們不再是 JBoss.org 一部分以來,Netty 的套件名稱已從 org.jboss.netty 變更為 io.netty

二進位 JAR 已拆分為多個次模組,因此使用者可以排除非必要的 class 路徑功能。目前結構如下所示

人工製品 ID 說明
netty-parent Maven 父系 POM
netty-common 公用程式類別和記錄門面
netty-buffer 取代 java.nio.ByteBufferByteBuf API
netty-transport Channel API 和主要傳輸
netty-transport-rxtx Rxtx 傳輸
netty-transport-sctp SCTP 傳輸
netty-transport-udt UDT 傳輸
netty-handler 有用的 ChannelHandler 實作
netty-codec 編解碼器架構有助於撰寫編碼器和解碼器
netty-codec-http 與 HTTP、網頁 Socket、SPDY 和 RTSP 相關的編解碼器
netty-codec-socks 與 SOCKS 協定相關的編解碼器
netty-all 將以上所有人工製品結合於一個 JAR 中
netty-tarball Tarball 散發
netty-example 範例
netty-testsuite-* 整合測試集
netty-microbench 微基準

除了 netty-all.jar 以外,所有人工製品現在都是 OSGi 函式庫,可使用在您最愛的 OSGi 容器中。

一般 API 變更

  • Netty 中多數運算現在支援方法串接,以求簡潔。
  • 非組態 getter 已不再有 get- 的前綴。例如:Channel.getRemoteAddress()Channel.remoteAddress()
    • 布林屬性仍會加上 is- 前綴,以免混淆(例如:「empty」同時是形容詞和動詞,因此 empty() 可能會有兩種意思)。
  • 若要瞭解 4.0 CR4 和 4.0 CR5 之間的 API 變更,請參閱 Netty 4.0.0.CR5 已發布,並搭載新的新 API

Buffer API 變更

ChannelBufferByteBuf

拜前面提到的結構性變更所賜,Buffer API 可作為獨立的套件來使用。即使您不想採用 Netty 作為網路應用程式架構,仍可使用我們的 Buffer API。因此,類型名稱 ChannelBuffer 已不再有意義,並已重新命名為 ByteBuf

輔助類別 ChannelBuffers 可建立新的 buffer,已被分割成兩個輔助類別,UnpooledByteBufUtil。正如其名 Unpooled 所示,4.0 採用了 pooled ByteBuf,可透過 ByteBufAllocator 實作來配置。

ByteBuf 不是介面,而是抽象類別

根據我們的內部效能測試,將 ByteBuf 從介面轉換成抽象類別,總吞吐量提升了約 5%。

多數 buffer 都具備最大容量

在 3.x 中,buffer 是固定的或動態的。固定 buffer 的容量在建立後便不會改變,而動態 buffer 的容量則會在 write*(...) 方法需要更多空間時變更。

從 4.0 開始,所有 buffer 都是動態的。不過,它們比舊有的動態 buffer 更棒。您可以更輕鬆、更安全地減小或增加 buffer 的容量。它很輕鬆是因為有了新的方法 ByteBuf.capacity(int newCapacity)。它很安全是因為您可以設定 buffer 的最大容量,讓它不會無限制地成長。

// No more dynamicBuffer() - use buffer().
ByteBuf buf = Unpooled.buffer();

// Increase the capacity of the buffer.
buf.capacity(1024);
...

// Decrease the capacity of the buffer (the last 512 bytes are deleted.)
buf.capacity(512);

唯一的例外是由 wrappedBuffer() 產生的封裝單一緩衝區或單一位元組陣列的緩衝區。您無法增加其容量,因為它會使封裝現有緩衝區的目的完全失效,而封裝的目的是為了節省記憶體副本。如果您在封裝緩衝區後想要變更容量,應該只要建立一個具有足夠容量的新緩衝區並複製要封裝的緩衝區即可。

新的緩衝區類型:CompositeByteBuf

一個稱為 CompositeByteBuf 的新緩衝區實作定義了針對複合緩衝區實作的各種進階作業。使用者可以使用複合緩衝區來節省大量記憶體複製作業,代價是隨機存取的成本相對高昂。要建立新的複合緩衝區,請使用 Unpooled.wrappedBuffer(...)(如之前所示)、Unpooled.compositeBuffer(...)ByteBufAllocator.compositeBuffer()

可預測的 NIO 緩衝區轉換

在 3.x 中,ChannelBuffer.toByteBuffer() 與其變體合約不夠確定性。使用者無法知道它們會傳回與共用資料的檢視緩衝區,或與分開資料的複製緩衝區。4.0 以 ByteBuf.nioBufferCount()nioBuffer()nioBuffers() 取代 toByteBuffer()。如果 nioBufferCount() 傳回 0,使用者總是可以在呼叫 copy().nioBuffer() 後取得複製緩衝區。

小端序支援變更

小端序支援已有 σημαν地更改。先前,使用者必須指定 LittleEndianHeapChannelBufferFactory,或以想要的位元組順序封裝現有緩衝區,才能取得小端序緩衝區。4.0 新增一個新方法:ByteBuf.order(ByteOrder),它會傳回呼叫者檢視所需位元組順序的檢視。

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.nio.ByteOrder;
 
ByteBuf buf = Unpooled.buffer(4);
buf.setInt(0, 1);
// Prints '00000001'
System.out.format("%08x%n", buf.getInt(0)); 
 
ByteBuf leBuf = buf.order(ByteOrder.LITTLE_ENDIAN);
// Prints '01000000'
System.out.format("%08x%n", leBuf.getInt(0));
 
assert buf != leBuf;
assert buf == buf.order(ByteOrder.BIG_ENDIAN);

已建立池的緩衝區

Netty 4 採用一個高效能緩衝區池,它結合了 jemalloc 的一個變體(結合了 buddy 配置slab 配置)。這提供了下列好處:

  • 減少頻繁配置與解除配置緩衝區造成的 GC 壓力
  • 減少建立新緩衝區(必然會填入零)時消耗的記憶體頻寬
  • 適時解除配置直接緩衝區

要利用這項功能,除非使用者想要取得未建立池的緩衝區,否則應該從 ByteBufAllocator 取得緩衝區。

Channel channel = ...;
ByteBufAllocator alloc = channel.alloc();
ByteBuf buf = alloc.buffer(512);
....
channel.write(buf);
 
ChannelHandlerContext ctx = ...
ByteBuf buf2 = ctx.alloc().buffer(512);
....
channel.write(buf2)

一旦將 ByteBuf 寫入至遠端同儕,它會自動傳回原始建立的池。

預設 ByteBufAllocatorPooledByteBufAllocator。如果您不想使用緩衝區建立池,或使用自己的配置器,請在 Channel.config().setAllocator(...) 使用替代配置器,例如 UnpooledByteBufAllocator

備註:當下預設的分配器為 UnpooledByteBufAllocator。一旦確認 PooledByteBufAllocator 中沒有記憶體外洩的情況,我們會再次將其設為預設值。

ByteBuf 永遠都記錄引用次數

為了更預測性地控制 ByteBuf 的生命週期,Netty 不再依賴垃圾收集程式,而是採用明確的引用計數器。以下為基本規則

  • 當配置緩衝區時,其初始引用次數為 1。
  • 如果緩衝區的引用次數降至 0,則會取消配置或將其傳回產生它的池。
  • 以下嘗試動作會觸發 IllegalReferenceCountException
    • 取用引用次數為 0 的緩衝區,
    • 將引用次數減少至負值,或
    • 將引用次數增至超過 Integer.MAX_VALUE
  • 衍生緩衝區(例如區塊和複製品)和交換緩衝區(即小尾數位組緩衝區)會與緩衝區共用由其衍生的引用次數。請注意,當產生衍生緩衝區時,引用次數並不會改變。

ByteBuf 用於 ChannelPipeline 時,需要謹記其他規則

  • 管線中的每個內建(亦稱上游)處理程式都必須發布收到的訊息。Netty 不會自動為您發布它們。
    • 請注意,編解碼器架構會自動發布訊息,而且使用者必須增加引用次數,才能原樣傳遞訊息至下一個處理程式。
  • 當外建(亦稱下游)訊息到達管線開頭時,Netty 會在寫出資訊後,將其發布。

自動緩衝區外洩偵測

儘管引用計數非常強大,但它也容易出錯。為幫助使用者找出未發布緩衝區的所在位置,外洩偵測器會自動記錄外洩緩衝區配置位置的堆疊追蹤。

由於外洩偵測器依賴 PhantomReference,而且取得堆疊追蹤是一項非常昂貴的作業,因此它僅對約 1% 的配置進行抽樣。因此,建議您執行應用程式達一定時間,找出所有可能的外洩。

在找出並修復所有外洩後,您可以透過指定 -Dio.netty.noResourceLeakDetection JVM 選項,完全關閉此功能,以移除其執行階段開銷。

io.netty.util.concurrent

4.0 除了新的獨立緩衝器 API 之外,還提供稱為 io.netty.util.concurrent 的新套件中的各種建構函式,這些建構函式的用途,在於協助撰寫非同步應用程式。這些建構函式包括

  • FuturePromise - 類似於 ChannelFuture,但與 Channel 無關
  • EventExecutorEventExecutorGroup - 通用事件迴圈 API

它們用作頻道 API 的基礎,其說明在本文檔後面。例如,ChannelFuture 延伸 io.netty.util.concurrent.FutureEventLoopGroup 延伸 EventExecutorGroup

Event loop type hierarchy diagram

頻道 API 變更

在 4.0 中,io.netty.channel 套件中許多類別都經過重大的修改,因此,單純的文字搜尋和替換方式無法使您的 3.x 應用程式與 4.0 相容。此區段試著顯示背後如此重大變更的思考流程,並非要詳列所有變更資源。

修改後的 ChannelHandler 介面

上行 → 輸入,下行 → 輸出

對於初學者而言,「上行」和「下行」這些術語相當令人混淆。4.0 盡可能使用「輸入」和「輸出」。

新的 ChannelHandler 類型層級

在 3.x 中,ChannelHandler 僅是一個標記介面,而 ChannelUpstreamHandlerChannelDownstreamHandler、和 LifeCycleAwareChannelHandler 定義實際的處理方法。在 Netty 4 中,ChannelHandler 合併 LifeCycleAwareChannelHandler 以及其他一些對輸入和輸出處理常式有用的方法。

public interface ChannelHandler {
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

以下圖示描繪新的類型層級

ChannelHandler type hierarchy diagram

沒有事件物件的 ChannelHandler

在 3.x 中,每個 I/O 作業都會建立一個 ChannelEvent 物件。對於每次讀取/寫入,它另外都會建立一個新的 ChannelBuffer。它相當簡化了 Netty 的內部結構,因為它將資源管理和緩衝池委派給 JVM。然而,它經常是 GC 負擔和不確定性的根源,這種情況有時會在高負載的 Netty 應用程式中觀察到。

4.0 幾乎完全移除事件物件的建立,方法是使用強型別方法呼叫取代事件物件。3.x 有萬用事件處理常式方法,例如 handleUpstream()handleDownstream(),但現在不再使用這種方式。現在,每種事件類型都有它自己的處理常式方法。

// Before:
void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e);
void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e);
 
// After:
void channelRegistered(ChannelHandlerContext ctx);
void channelUnregistered(ChannelHandlerContext ctx);
void channelActive(ChannelHandlerContext ctx);
void channelInactive(ChannelHandlerContext ctx);
void channelRead(ChannelHandlerContext ctx, Object message);
 
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise);
void connect(
        ChannelHandlerContext ctx, SocketAddress remoteAddress,
        SocketAddress localAddress, ChannelPromise promise);
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise);
void close(ChannelHandlerContext ctx, ChannelPromise promise);
void deregister(ChannelHandlerContext ctx, ChannelPromise promise);
void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise);
void flush(ChannelHandlerContext ctx);
void read(ChannelHandlerContext ctx);

ChannelHandlerContext 也已被變更以反映以上所提的變更。

// Before:
ctx.sendUpstream(evt);
 
// After:
ctx.fireChannelRead(receivedMessage);

所有這些變更表示使用者無法再延伸非存在的 ChannelEvent 介面。那麼使用者要如何定義自己的事件類型,例如 IdleStateEvent?4.0 中的 ChannelHandlerContextfireUserEventTriggered 方法可以用來觸發自訂事件而 ChannelInboundHandler 有一個叫做 userEventTriggered() 的處理常式方法專用於處理自訂事件的特定使用者案例。

簡化的頻道狀態模型

當在 3.x 建立新的連線 Channel 時,至少會觸發三個 ChannelStateEventchannelOpenchannelBound、和 channelConnected。當 Channel 關閉時,至少還有三個:channelDisconnectedchannelUnbound、和 channelClosed

Netty 3 Channel state diagram

然而,觸發這麼多事件的價值是有疑慮的。當 Channel 進入可以執行讀寫的狀態時,使用者通常希望收到通知。

Netty 4 Channel state diagram

channelOpenchannelBoundchannelConnected 已合併至 channelActivechannelDisconnectedchannelUnboundchannelClosed 已合併至 channelInactive。同樣地,Channel.isBound()isConnected() 也已合併至 isActive()

請注意,channelRegisteredchannelUnregistered 不等於 channelOpenchannelClosed。它們是為支援動態註冊、取消註冊以及 Channel 的重新註冊而新增的狀態,如下所示

Netty 4 Channel state diagram for re-registration

write() 不會自動執行沖刷

4.0 新增了一個稱為 flush() 的運算,會明確地沖刷 Channel 的輸出緩衝區,而 write() 運算不會自動沖刷。您可以認為這是一個 java.io.BufferedOutputStream,只不過它在訊息層級執行。

由於這個變更,您必須非常小心,在撰寫某些內容後不要忘記呼叫 ctx.flush()。或者,您可以使用 writeAndFlush() 捷徑方式。

合理且較不易出錯的入站流量暫停

3.x 有一個不直覺的入站流量暫停機制,由 Channel.setReadable(boolean) 提供。它引入了頻道處理程式之間的複雜互動,且處理程式之間在錯誤實作時很容易互相干擾。

在 4.0 中,新增了一個稱為 read() 的新輸出運算。如果您使用 Channel.config().setAutoRead(false) 關閉預設自動讀取旗標,Netty 就會停止讀取任何內容,直到您明確呼叫 read() 運算。您發出的 read() 運算完成且頻道再次停止讀取後,會觸發稱為 channelReadSuspended() 的入站事件,以便您可以重新發出另一個 read() 運算。您也可以攔截 read() 運算,以執行更進階的流量控制。

暫停接受連線

對使用者而言,並沒有一種方法可以指示 Netty 3.x 停止接受入站連線,只能封鎖 I/O 執行緒或關閉伺服器端套接字。當自動讀取旗標未設定時,4.0 會像一般頻道一樣尊重 read() 運算。

半關閉套接字

TCP 和 SCTP 允許使用者關閉套接字的輸出流量,而不是完全關閉它。這種套接字稱為「半關閉套接字」,使用者可以呼叫 SocketChannel.shutdownOutput() 方法 來建立半關閉套接字。如果遠端對等方關閉輸出流量,SocketChannel.read(..) 會傳回 -1,這看起來和關閉連線沒有什麼不同。

3.x 沒有 shutdownOutput() 運算。而且當 SocketChannel.read(..) 傳回 -1 時,它總是會關閉連線。

為了支援半關閉連接,4.0 加入 SocketChannel.shutdownOutput() 方法,而且使用者可以設定 ChannelOption 的『ALLOW_HALF_CLOSURE』,避免 Netty 在 SocketChannel.read(..) 回傳 -1 時,自動關閉連線。

彈性 I/O 執行緒配置

在 3.x 中,由 ChannelFactory 建立 Channel,而新建立的 Channel 會自動註冊到隱藏的 I/O 執行緒。4.0 使用由一個或多個 EventLoop 組成的 EventLoopGroup 新介面取代 ChannelFactory。另外,新的 Channel 沒有自動註冊到 EventLoopGroup,而是必須由使用者明確呼叫 EventLoopGroup.register()

因為此項變更(亦即 ChannelFactory 和 I/O 執行緒分開),使用者可以將不同的 Channel 實作註冊到同一個 EventLoopGroup,或是將同一個 Channel 實作註冊到不同的 EventLoopGroup。例如,您可以在同一個 I/O 執行緒執行 NIO 伺服器 Socket、NIO 執行緒 Socket、NIO UDP Socket 和 VM 內部區域通道。當撰寫需要最小延遲的 Proxy 伺服器時,應該會非常實用。

從現有的 JDK socket 建立 Channel 的能力

3.x 無法從現有的 JDK socket(例如 java.nio.channels.SocketChannel)建立新的 Channel。而 4.0 可以。

從 I/O 執行緒註銷並重新註冊 Channel

在 3.x 中建立新的 Channel 後,直到其底層 socket 關閉為止,會整個繫結到單一的 I/O 執行緒。在 4.0 中,使用者可以從其 I/O 執行緒註銷 Channel,以取得其底層 JDK socket 的完整控制權。例如,您可以利用 Netty 提供的高階非阻擋 I/O 來處理複雜的通訊協定,接著註銷 Channel 並切換到阻擋模式,以可能的最大傳輸量來傳輸檔案。當然,可以重新註冊已註銷的 Channel

java.nio.channels.FileChannel myFile = ...;
java.nio.channels.SocketChannel mySocket = java.nio.channels.SocketChannel.open();
 
// Perform some blocking operation here.
...
 
// Netty takes over.
SocketChannel ch = new NioSocketChannel(mySocket);
EventLoopGroup group = ...;
group.register(ch);
...
 
// Deregister from Netty.
ch.deregister().sync();
 
// Perform some blocking operation here.
mySocket.configureBlocking(true);
myFile.transferFrom(mySocket, ...);
 
// Register back again to another event loop group.
EventLoopGroup anotherGroup = ...;
anotherGroup.register(ch);

排程任意的工作以由 I/O 執行緒執行

Channel 註冊到 EventLoopGroup 時,Channel 實際上是註冊到一個由 EventLoopGroup 管理的 EventLoopEventLoop 實作 java.util.concurrent.ScheduledExecutorService。這表示使用者可以在執行緒的通道中執行或排程任意的 RunnableCallable。再加上新的定義明確的執行緒模型(稍後說明),可以更輕鬆地撰寫執行緒安全處理常式。

public class MyHandler extends ChannelOutboundHandlerAdapter {
    ...
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise p) {
        ...
        ctx.write(msg, p);
        
        // Schedule a write timeout.
        ctx.executor().schedule(new MyWriteTimeoutTask(p), 30, TimeUnit.SECONDS);
        ...
    }
}
 
public class Main {
    public static void main(String[] args) throws Exception {
        // Run an arbitrary task from an I/O thread.
        Channel ch = ...;
        ch.executor().execute(new Runnable() { ... });
    }
}

簡化關閉

不再有 releaseExternalResources()。您可以立即關閉所有開啟的通道,並呼叫 EventLoopGroup.shutdownGracefully(),讓所有 I/O 執行緒停止執行。

具備型別安全性 ChannelOption

在 Netty 中有兩種方法可以設定頻道的 socket 參數。一種方法是明確呼叫設定器ChannelConfig,例如SocketChannelConfig.setTcpNoDelay(true)。這是類型最安全的途徑。另一種方法是呼叫ChannelConfig.setOption()方法。有時候你必須在執行階段決定設定哪些 socket 選項,而此方法在遇到這種狀況時是最理想的。然而,在 3.x 中,它很容易出錯,因為使用者必須指定選項為一對字串和物件。如果使用者呼叫錯誤的選項名稱或值,他或她會遇到ClassCastException,或指定的選項甚至可能會被靜默忽略。

4.0 引進一個新類型,稱為ChannelOption,它提供類型安全的 socket 選項存取。

ChannelConfig cfg = ...;
 
// Before:
cfg.setOption("tcpNoDelay", true);
cfg.setOption("tcpNoDelay", 0);  // Runtime ClassCastException
cfg.setOption("tcpNoDelays", true); // Typo in the option name - ignored silently
 
// After:
cfg.setOption(ChannelOption.TCP_NODELAY, true);
cfg.setOption(ChannelOption.TCP_NODELAY, 0); // Compile error

AttributeMap

針對使用者的需求,你可以將任何物件附加到ChannelChannelHandlerContext。已經新增一個新介面,稱為AttributeMap,這是一個ChannelChannelHandlerContext延伸所使用的介面。相對而言,ChannelLocalChannel.attachment已移除。這些屬性會在關聯的Channel被回收時被垃圾回收。

public class MyHandler extends ChannelInboundHandlerAdapter {
 
    private static final AttributeKey<MyState> STATE =
            AttributeKey.valueOf("MyHandler.state");
 
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        ctx.attr(STATE).set(new MyState());
        ctx.fireChannelRegistered();
    }
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MyState state = ctx.attr(STATE).get();
    }
    ...
}

新的 Bootstrap API

Bootstrap API 已從頭開始重寫,雖然其用途仍然相同,它會執行常見步驟:讓伺服器或客戶端隨時準備就緒,通常會在樣板程式碼中找到。

新的 Bootstrap 也採用流暢介面。

public static void main(String[] args) throws Exception {
    // Configure the server.
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .option(ChannelOption.SO_BACKLOG, 100)
         .localAddress(8080)
         .childOption(ChannelOption.TCP_NODELAY, true)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ch.pipeline().addLast(handler1, handler2, ...);
             }
         });
 
        // Start the server.
        ChannelFuture f = b.bind().sync();
 
        // Wait until the server socket is closed.
        f.channel().closeFuture().sync();
    } finally {
        // Shut down all event loops to terminate all threads.
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
        
        // Wait until all threads are terminated.
        bossGroup.terminationFuture().sync();
        workerGroup.terminationFuture().sync();
    }
}

ChannelPipelineFactoryChannelInitializer

如上例所示,ChannelPipelineFactory已不復存在。它已被ChannelInitializer所取代,其提供對ChannelChannelPipeline設定的更多控制權。

請注意,你不需要自己建立新的ChannelPipeline。在觀察很多到目前為止回報的使用案例之後,Netty 專案團隊總結出,使用者建立自己的管線實作或延伸預設實作對使用者而言沒有任何好處。因此,不再由使用者建立 ChannelPipelineChannelPipeline會自動由Channel所建立。

ChannelFutureChannelFutureChannelPromise

ChannelFuture已拆分為ChannelFutureChannelPromise。這不僅會讓非同步作業的消費者和生產者契約更明確,而且也會讓返回的ChannelFuture在一個鏈中使用(例如篩選)時更安全,因為ChannelFuture的狀態無法變更。

由於有了這項變更,某些方法現在會接受ChannelPromise,而非ChannelFuture來變更其狀態。

定義明確的執行緒模型

3.x 中沒有定義完善的執行緒模型,雖然嘗試在 3.5 中修正不一致的問題。4.0 定義了嚴格的執行緒模型,讓使用者可以撰寫 ChannelHandler 時不必過於擔心執行緒安全性。

  • 除非 ChannelHandler 加上註解 @Sharable,否則 Netty 永遠不會同時呼叫 ChannelHandler 的方法。此限制不論處理程序方法的類型為何,包括輸入、輸出或生命週期事件處理程序方法。
    • 使用者不再需要同步輸入或輸出事件處理程序方法。
    • 4.0 禁止重複新增 ChannelHandler,除非加上註解 @Sharable
  • 每次 Netty 呼叫 ChannelHandler 方法時,一定會建立 happens-before 關係。
    • 使用者不需要定義 volatile 欄位來保留處理程序的狀態。
  • 使用者可以在 ChannelPipeline 中新增處理程序時,指定一個 EventExecutor。
    • 如果已指定,則 ChannelHandler 的處理程序方法總是會由指定的 EventExecutor 呼叫。
    • 如果不指定,處理程序會由關聯的 Channel 所註冊的 EventLoop 呼叫。
  • 指定給處理程序或頻道的 EventExecutor 和 EventLoop 始終為單執行緒。
    • 處理程序方法一定會由同一個執行緒呼叫。
    • 如果指定了多執行緒的 EventExecutor 或 EventLoop,會先選擇其中一個執行緒,然後在取消註冊前,一直使用這個執行緒。
    • 如果將同一個管道中的兩個處理程序指定到不同的 EventExecutor,它們會同時被呼叫。如果一個以上的處理程序存取共用資料,就算共用資料只會被同一個管道中的處理程序存取,使用者仍必須注意執行緒安全性。
  • 已新增到 ChannelFuture 的 ChannelFutureListeners 始終會由指定給 future 關聯的 Channel 的 EventLoop 執行緒呼叫。
  • 可以使用 ChannelHandlerInvoker 來控制 Channel 事件的順序。DefaultChannelHandlerInvoker 會立即從 EventLoop 執行緒執行事件,並在 EventExecutor 上將其他執行緒的事件執行為 Runnable 物件。請參閱下面的範例,了解當從 EventLoop 執行緒和其他執行緒與頻道互動時,可能會產生的影響。(此功能已被移除。請參閱 相關提交記錄)
寫入順序 - 混合使用 EventLoop 執行緒和其他執行緒
Channel ch = ...;
ByteBuf a, b, c = ...;

// From Thread 1 - Not the EventLoop thread
ch.write(a);
ch.write(b);

// .. some other stuff happens

// From EventLoop Thread
ch.write(c);

// The order a, b, and c will be written to the underlying transport is not well
// defined. If order is important, and this threading interaction occurs, it is
// the user's responsibility to enforce ordering.

不再有 ExecutionHandler - 已經整合到核心。

您可以向一個ChannelPipeline新增一個ChannelHandler,並指定一個EventExecutor,以指示執行序管線應始終透過指定的EventExecutor,來呼叫已新增ChannelHandler的處理常式方法。

Channel ch = ...;
ChannelPipeline p = ch.pipeline();
EventExecutor e1 = new DefaultEventExecutor(16);
EventExecutor e2 = new DefaultEventExecutor(8);
 
p.addLast(new MyProtocolCodec());
p.addLast(e1, new MyDatabaseAccessingHandler());
p.addLast(e2, new MyHardDiskAccessingHandler());

編碼解碼架構的變更

4.0 版需要一個處理常式來建立和管理其緩衝,因此編碼解碼架構中有大量的內部變更(請參閱本文中的「每個處理常式的緩衝」章節)。不過,從使用者的角度來看,變更並不大。

  • 核心編碼解碼類別已移至io.netty.handler.codec套件。
  • FrameDecoder已重新命名為ByteToMessageDecoder
  • OneToOneEncoderOneToOneDecoder已被MessageToMessageEncoderMessageToMessageDecoder取代。
  • decode()decodeLast()encode()的方法簽章已稍微變更,以支援泛型並移除多餘的參數。

編碼解碼嵌入器 → EmbeddedChannel

編碼解碼嵌入器已由io.netty.channel.embedded.EmbeddedChannel取代,可讓使用者測試任何包含編碼解碼器的執行序管線。

HTTP 編碼解碼器

HTTP 解碼器現在會針對每個單一 HTTP 訊息產生多個訊息物件。

1       * HttpRequest / HttpResponse
0 - n   * HttpContent
1       * LastHttpContent

如需更詳細資訊,請參閱已更新的HttpSnoopServer範例。如果您不希望為每個單一 HTTP 訊息處理多個訊息,您可以在執行序管線中放入一個HttpObjectAggregatorHttpObjectAggregator會將多個訊息轉換成一個單一的FullHttpRequestFullHttpResponse

傳輸實作中的變更

已新增下列傳輸:

  • OIO SCTP 傳輸
  • UDT 傳輸

個案研究:移植階乘範例

本節說明將階乘範例從 3.x 移植至 4.0 的步驟。階乘範例已移植至 4.0,位在io.netty.example.factorial套件中。請瀏覽範例的原始程式碼,以找出所有變更的細項。

移植伺服器

  1. 重新撰寫FactorialServer.run()方法,以使用新的引導程式 API。
  2. 沒有更多ChannelFactory。自行建立NioEventLoopGroup(一個用於接受即將連線的連線,另一個用於處理已接受的連線)。
  3. FactorialServerPipelineFactory重新命名為FactorialServerInitializer
  4. 讓它延伸ChannelInitializer<Channel>
  5. 取得新的ChannelPipeline是透過Channel.pipeline(),而不是透過建立一個新的ChannelPipeline
  6. FactorialServerHandler延伸ChannelInboundHandlerAdapter
  7. 使用channelInactive()取代channelDisconnected()
  8. 不再使用handleUpstream()。
  9. messageReceived()重新命名為channelRead(),並針對方法簽章進行調整。
  10. 使用ctx.writeAndFlush()取代ctx.write()
  11. BigIntegerDecoder延伸ByteToMessageDecoder<BigInteger>
  12. NumberEncoder延伸MessageToByteEncoder<Number>
  13. encode()不再傳回緩衝區。將編碼資料填入由ByteToMessageDecoder提供的緩衝區中。

移植用戶端

主要與移植伺服器相同,但當您寫入可能很大的串流時,需要特別注意。

  1. 重新編寫 FactorialClient.run() 方法,以使用新的引導 API。
  2. FactorialClientPipelineFactory 重新命名為 FactorialClientInitializer
  3. FactorialClientHandler 延伸 ChannelInboundHandlerAdapter
最後檢索時間:2024-07-19