4.0 新功能與注意事項
本文件將引導您瀏覽 Netty 主要版本的重大變更和新功能清單,讓您了解如何將應用程式移植到新版本。
自我們不再是 JBoss.org 一部分以來,Netty 的套件名稱已從 org.jboss.netty
變更為 io.netty
。
二進位 JAR 已拆分為多個次模組,因此使用者可以排除非必要的 class 路徑功能。目前結構如下所示
人工製品 ID | 說明 |
---|---|
netty-parent |
Maven 父系 POM |
netty-common |
公用程式類別和記錄門面 |
netty-buffer |
取代 java.nio.ByteBuffer 的 ByteBuf 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 容器中。
- 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 可作為獨立的套件來使用。即使您不想採用 Netty 作為網路應用程式架構,仍可使用我們的 Buffer API。因此,類型名稱 ChannelBuffer
已不再有意義,並已重新命名為 ByteBuf
。
輔助類別 ChannelBuffers
可建立新的 buffer,已被分割成兩個輔助類別,Unpooled
和 ByteBufUtil
。正如其名 Unpooled
所示,4.0 採用了 pooled ByteBuf
,可透過 ByteBufAllocator
實作來配置。
根據我們的內部效能測試,將 ByteBuf
從介面轉換成抽象類別,總吞吐量提升了約 5%。
在 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
的新緩衝區實作定義了針對複合緩衝區實作的各種進階作業。使用者可以使用複合緩衝區來節省大量記憶體複製作業,代價是隨機存取的成本相對高昂。要建立新的複合緩衝區,請使用 Unpooled.wrappedBuffer(...)
(如之前所示)、Unpooled.compositeBuffer(...)
或 ByteBufAllocator.compositeBuffer()
。
在 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
寫入至遠端同儕,它會自動傳回原始建立的池。
預設 ByteBufAllocator
是 PooledByteBufAllocator
。如果您不想使用緩衝區建立池,或使用自己的配置器,請在 Channel.config().setAllocator(...)
使用替代配置器,例如 UnpooledByteBufAllocator
。
備註:當下預設的分配器為 UnpooledByteBufAllocator
。一旦確認 PooledByteBufAllocator
中沒有記憶體外洩的情況,我們會再次將其設為預設值。
為了更預測性地控制 ByteBuf
的生命週期,Netty 不再依賴垃圾收集程式,而是採用明確的引用計數器。以下為基本規則
- 當配置緩衝區時,其初始引用次數為 1。
- 如果緩衝區的引用次數降至 0,則會取消配置或將其傳回產生它的池。
- 以下嘗試動作會觸發
IllegalReferenceCountException
- 取用引用次數為 0 的緩衝區,
- 將引用次數減少至負值,或
- 將引用次數增至超過
Integer.MAX_VALUE
。
- 衍生緩衝區(例如區塊和複製品)和交換緩衝區(即小尾數位組緩衝區)會與緩衝區共用由其衍生的引用次數。請注意,當產生衍生緩衝區時,引用次數並不會改變。
當 ByteBuf
用於 ChannelPipeline
時,需要謹記其他規則
- 管線中的每個內建(亦稱上游)處理程式都必須發布收到的訊息。Netty 不會自動為您發布它們。
- 請注意,編解碼器架構會自動發布訊息,而且使用者必須增加引用次數,才能原樣傳遞訊息至下一個處理程式。
- 當外建(亦稱下游)訊息到達管線開頭時,Netty 會在寫出資訊後,將其發布。
儘管引用計數非常強大,但它也容易出錯。為幫助使用者找出未發布緩衝區的所在位置,外洩偵測器會自動記錄外洩緩衝區配置位置的堆疊追蹤。
由於外洩偵測器依賴 PhantomReference
,而且取得堆疊追蹤是一項非常昂貴的作業,因此它僅對約 1% 的配置進行抽樣。因此,建議您執行應用程式達一定時間,找出所有可能的外洩。
在找出並修復所有外洩後,您可以透過指定 -Dio.netty.noResourceLeakDetection
JVM 選項,完全關閉此功能,以移除其執行階段開銷。
4.0 除了新的獨立緩衝器 API 之外,還提供稱為 io.netty.util.concurrent
的新套件中的各種建構函式,這些建構函式的用途,在於協助撰寫非同步應用程式。這些建構函式包括
-
Future
和Promise
- 類似於ChannelFuture
,但與Channel
無關 -
EventExecutor
和EventExecutorGroup
- 通用事件迴圈 API
它們用作頻道 API 的基礎,其說明在本文檔後面。例如,ChannelFuture
延伸 io.netty.util.concurrent.Future
而 EventLoopGroup
延伸 EventExecutorGroup
。
在 4.0 中,io.netty.channel
套件中許多類別都經過重大的修改,因此,單純的文字搜尋和替換方式無法使您的 3.x 應用程式與 4.0 相容。此區段試著顯示背後如此重大變更的思考流程,並非要詳列所有變更資源。
對於初學者而言,「上行」和「下行」這些術語相當令人混淆。4.0 盡可能使用「輸入」和「輸出」。
在 3.x 中,ChannelHandler
僅是一個標記介面,而 ChannelUpstreamHandler
、ChannelDownstreamHandler
、和 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;
}
以下圖示描繪新的類型層級
在 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 中的 ChannelHandlerContext
有 fireUserEventTriggered
方法可以用來觸發自訂事件而 ChannelInboundHandler
有一個叫做 userEventTriggered()
的處理常式方法專用於處理自訂事件的特定使用者案例。
當在 3.x 建立新的連線 Channel
時,至少會觸發三個 ChannelStateEvent
:channelOpen
、channelBound
、和 channelConnected
。當 Channel
關閉時,至少還有三個:channelDisconnected
、channelUnbound
、和 channelClosed
。
然而,觸發這麼多事件的價值是有疑慮的。當 Channel
進入可以執行讀寫的狀態時,使用者通常希望收到通知。
channelOpen
、channelBound
和 channelConnected
已合併至 channelActive
。channelDisconnected
、channelUnbound
和 channelClosed
已合併至 channelInactive
。同樣地,Channel.isBound()
和 isConnected()
也已合併至 isActive()
。
請注意,channelRegistered
和 channelUnregistered
不等於 channelOpen
和 channelClosed
。它們是為支援動態註冊、取消註冊以及 Channel
的重新註冊而新增的狀態,如下所示
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
時,自動關閉連線。
在 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 伺服器時,應該會非常實用。
3.x 無法從現有的 JDK socket(例如 java.nio.channels.SocketChannel
)建立新的 Channel。而 4.0 可以。
在 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);
當 Channel
註冊到 EventLoopGroup
時,Channel
實際上是註冊到一個由 EventLoopGroup
管理的 EventLoop
。EventLoop
實作 java.util.concurrent.ScheduledExecutorService
。這表示使用者可以在執行緒的通道中執行或排程任意的 Runnable
或 Callable
。再加上新的定義明確的執行緒模型(稍後說明),可以更輕鬆地撰寫執行緒安全處理常式。
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 執行緒停止執行。
在 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
針對使用者的需求,你可以將任何物件附加到Channel
和ChannelHandlerContext
。已經新增一個新介面,稱為AttributeMap
,這是一個Channel
和ChannelHandlerContext
延伸所使用的介面。相對而言,ChannelLocal
和Channel.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 也採用流暢介面。
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();
}
}
如上例所示,ChannelPipelineFactory
已不復存在。它已被ChannelInitializer
所取代,其提供對Channel
和ChannelPipeline
設定的更多控制權。
請注意,你不需要自己建立新的ChannelPipeline
。在觀察很多到目前為止回報的使用案例之後,Netty 專案團隊總結出,使用者建立自己的管線實作或延伸預設實作對使用者而言沒有任何好處。因此,不再由使用者建立 ChannelPipeline
。ChannelPipeline
會自動由Channel
所建立。
ChannelFuture
已拆分為ChannelFuture
和ChannelPromise
。這不僅會讓非同步作業的消費者和生產者契約更明確,而且也會讓返回的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 執行緒和其他執行緒與頻道互動時,可能會產生的影響。(此功能已被移除。請參閱 相關提交記錄)
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.
您可以向一個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
。 -
OneToOneEncoder
和OneToOneDecoder
已被MessageToMessageEncoder
和MessageToMessageDecoder
取代。 decode()
、decodeLast()
、encode()
的方法簽章已稍微變更,以支援泛型並移除多餘的參數。
編碼解碼嵌入器已由io.netty.channel.embedded.EmbeddedChannel
取代,可讓使用者測試任何包含編碼解碼器的執行序管線。
HTTP 解碼器現在會針對每個單一 HTTP 訊息產生多個訊息物件。
1 * HttpRequest / HttpResponse
0 - n * HttpContent
1 * LastHttpContent
如需更詳細資訊,請參閱已更新的HttpSnoopServer
範例。如果您不希望為每個單一 HTTP 訊息處理多個訊息,您可以在執行序管線中放入一個HttpObjectAggregator
。HttpObjectAggregator
會將多個訊息轉換成一個單一的FullHttpRequest
或FullHttpResponse
。
已新增下列傳輸:
- OIO SCTP 傳輸
- UDT 傳輸
本節說明將階乘範例從 3.x 移植至 4.0 的步驟。階乘範例已移植至 4.0,位在io.netty.example.factorial
套件中。請瀏覽範例的原始程式碼,以找出所有變更的細項。
- 重新撰寫
FactorialServer.run()
方法,以使用新的引導程式 API。 - 沒有更多
ChannelFactory
。自行建立NioEventLoopGroup
(一個用於接受即將連線的連線,另一個用於處理已接受的連線)。 - 將
FactorialServerPipelineFactory
重新命名為FactorialServerInitializer
。 - 讓它延伸
ChannelInitializer<Channel>
。 - 取得新的
ChannelPipeline
是透過Channel.pipeline()
,而不是透過建立一個新的ChannelPipeline
。 - 讓
FactorialServerHandler
延伸ChannelInboundHandlerAdapter
。 - 使用
channelInactive()
取代channelDisconnected()
。 - 不再使用handleUpstream()。
- 將
messageReceived()
重新命名為channelRead()
,並針對方法簽章進行調整。 - 使用
ctx.writeAndFlush()
取代ctx.write()
。 - 讓
BigIntegerDecoder
延伸ByteToMessageDecoder<BigInteger>
。 - 讓
NumberEncoder
延伸MessageToByteEncoder<Number>
。 -
encode()
不再傳回緩衝區。將編碼資料填入由ByteToMessageDecoder
提供的緩衝區中。
主要與移植伺服器相同,但當您寫入可能很大的串流時,需要特別注意。
- 重新編寫
FactorialClient.run()
方法,以使用新的引導 API。 - 將
FactorialClientPipelineFactory
重新命名為FactorialClientInitializer
。 - 讓
FactorialClientHandler
延伸ChannelInboundHandlerAdapter