官方文檔地址
New and noteworthy in 4.0
本文帶你了解Netty4中值得關注的變化及新特性,它會幫助你在應用中使用這個新版本。
項目結構變更
Netty的軟件包名稱已從org.jboss.netty
更改為io.netty
因為我們不再屬於JBoss.org。
二進制JAR已拆分為多個子模塊,因此用戶可以從類路徑中排除不必要的功能。當前結構如下:
Artifact ID | 描述 |
---|---|
netty-parent |
Maven父POM |
netty-common |
工具類和日志框架 |
netty-buffer |
替代 java.nio.ByteBuffer 的ByteBuf API |
netty-transport |
Channel API和核心傳輸core transports |
netty-transport-rxtx |
Rxtx傳輸 |
netty-transport-sctp |
SCTP傳輸 |
netty-transport-udt |
UDT傳輸 |
netty-handler |
有用的ChannelHandler 實現 |
netty-codec |
有助於編寫編碼器和解碼器的編解碼器框架 |
netty-codec-http |
與HTTP,Web套接字,SPDY和RTSP相關的編解碼器 |
netty-codec-socks |
與SOCKS協議相關的編解碼器 |
netty-all |
結合了以上所有模塊的多合一JAR |
netty-tarball |
Tarball發行版本 |
netty-example |
例子 |
netty-testsuite-* |
集成測試的集合 |
netty-microbench |
微基准 |
現在,所有Artifacts(除了netty-all.jar
)都是OSGi捆綁包,可以在您喜歡的OSGi容器中使用。
常規API更改
-
Netty中的大多數操作現在都支持鏈式調用。
-
非配置的get方法不再具有get-前綴。(例如Channel.getRemoteAddress()→Channel.remoteAddress())
-
布爾屬性仍帶有前綴
is-
以避免混淆(例如,“ empty”既是形容詞又是動詞,因此empty()
可以有兩種含義。) -
有關4.0 CR4和4.0 CR5之間的API更改,請參閱隨新API發布的Netty 4.0.0.CR5
緩沖區API更改
ChannelBuffer
→ ByteBuf
由於上述包結構上的更改,緩沖區API可以用作單獨的程序包。即使您不希望將Netty用作網絡應用程序框架,也可以使用我們的緩沖區API。因此,類型名稱ChannelBuffer
不再有意義,並且已重命名為ByteBuf
。
實用類ChannelBuffers
,它創建了一個新的緩沖區,已分裂成兩個實用工具類,Unpooled
以及ByteBufUtil
。從其名稱Unpooled
可以猜到,4.0引入了池化的ByteBuf
,可以通過ByteBufAllocator
實現進行分配。
ByteBuf
不是接口而是抽象類
根據我們的內部性能測試,將ByteBuf
從接口轉換為抽象類可將整體吞吐量提高約5%。
大多數緩沖區都是動態的,具有最大容量
在3.x中,緩沖區是固定的或動態的。固定緩沖區的容量一旦創建就不會更改,而動態緩沖區的容量只要其write*(...)
方法需要更多空間就會更改。
從4.0開始,所有緩沖區都是動態的。但是,它們比舊的動態緩沖區要好。您可以更輕松,更安全地減少或增加緩沖區的容量。這是容易做到的,是因為有一個新方法ByteBuf.capacity(int newCapacity)
。這是安全的,因為您可以設置緩沖區的最大容量,以使其不會無限增長。
//不再需要dynamicBuffer()-使用buffer()。
ByteBuf buf = Unpooled.buffer();
// 增加緩沖區大小
buf.capacity(1024);
...
//減少緩沖區的大小(刪除最后512個字節。)
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 allocation和slab allocation。它具有以下優點:
- 降低頻繁分配和重新分配緩沖區導致的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
采用引用計數
為了以一種更可預測的方式控制ByteBuf
的生命周期,Netty不再依賴垃圾回收器,而是使用了一個顯式的引用計數器。下面是基本規則:
-
分配緩沖區后,其初始引用計數為1。
-
如果緩沖區的引用計數減少到0,則將其重新分配或將其放回創建它的池中。
-
以下操作會觸發IllegalReferenceCountException:
- 訪問一個引用計數為0的緩沖區,
- 將引用計數減少為負值,或者
- 將參考計數增加到大於Integer.MAX_VALUE`。
-
派生緩沖區(例如,切片和重復項)和交換緩沖區(即小端序緩沖區)與派生它的緩沖區共享引用計數。請注意,創建派生緩沖區時,引用計數不會更改。
當ByteBuf
在ChannelPipeline
中使用時,您需要牢記額外的規則:
- 管道pipeline 中的每個入站inbound (又稱upstream)handler 都必須主動釋放接收到的消息。Netty不會自動為您釋放它們。
- 請注意,編解碼器框架確實會自動釋放消息,並且如果用戶想按原樣將消息傳遞給下一個處理程序,則用戶必須增加引用計數。
- 當出站outbound (又名downstream)消息到達管道pipeline的開頭時,Netty將其寫出后將其釋放。
緩沖區泄漏自動檢測
盡管引用計數功能非常強大,但也容易出錯。為了幫助用戶找到忘記釋放緩沖區的位置,泄漏檢測器記錄了泄漏緩沖區自動分配位置的堆棧跟蹤。
因為泄漏檢測器依賴PhantomReference
並且獲得堆棧跟蹤是非常昂貴的操作,所以它僅對分配的1%進行采樣。因此,最好在相當長的時間內運行應用程序以查找所有可能的泄漏。
找到並修復所有泄漏后,您可以通過指定-Dio.netty.noResourceLeakDetection
JVM選項來關閉此功能以完全消除其運行時開銷。
io.netty.util.concurrent
4.0與新的獨立緩沖區API一起,提供了各種類,這些類位於io.netty.util.concurrent
的新包,通常可用於編寫異步應用程序。其中一些類是:
Future
和Promise
-與ChannelFuture
相似,但不依賴於Channel
EventExecutor
和EventExecutorGroup
-通用事件循環API
它們用作channel API的基礎,本文檔后面將對此進行說明。例如,ChannelFuture
extends io.netty.util.concurrent.Future
和EventLoopGroup
extends EventExecutorGroup
。
Channel API變化
在4.0中,io.netty.channel
包下的許多類都進行了大修,因此簡單的文本搜索和替換將無法使您的3.x應用程序在4.0上正常工作。本節將試圖顯示出如此巨大的變化背后的思考過程,而不是成為所有變化的詳盡資源。
改進的ChannelHandler接口
上游Upstream →入站Inbound,下游Upstream →出站Outbound
對於初學者來說,“上游Upstream ”和“下游Upstream ”這兩個術語非常令人困惑。4.0盡可能使用“入站Inbound”和“出站Outbound”。
新的ChannelHandler
層次結構
在 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;
}
下圖描述了新的類型層次結構:
事件對象從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中ChannelInboundHandler
,有一個稱為userEventTriggered()
的處理方法專用於此特定用戶情況。
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
System.out.println("READER_IDLE");
// 超時關閉channel
ctx.close();
} else if (event.state().equals(IdleState.WRITER_IDLE)) {
System.out.println("WRITER_IDLE");
} else if (event.state().equals(IdleState.ALL_IDLE)) {
System.out.println("ALL_IDLE");
// 發送心跳
ctx.channel().write("ping\n");
}
}
super.userEventTriggered(ctx, evt);
}
簡化的通道狀態模型
當一個新的連接Channel
在3.X創建,至少有三個ChannelStateEvent
s的觸發:channelOpen
,channelBound
,和channelConnected
。當Channel
被關閉時,需要至少額外的3個:channelDisconnected
,channelUnbound
,和channelClosed
。
但是,觸發這么多事件讓人懷疑它的價值。當用戶Channel
進入可以執行讀寫操作的狀態時,通知用戶更為有用。
channelOpen
,channelBound
和channelConnected
已合並到channelActive
。channelDisconnected
,channelUnbound
和channelClosed
已合並到channelInactive
。同樣,Channel.isBound()
和isConnected()
已合並到isActive()
。
請注意,channelRegistered
和channelUnregistered
不等同於channelOpen
和channelClosed
。它們是引入的新狀態,以支持Channel
的動態注冊,注銷和重新注冊,如下所示:
Channel的write()
不會自動刷新
4.0引入了一個名為flush()
的新操作,該操作顯式刷新Channel
的出站緩沖區,並且該write()
操作不會自動刷新。您可以將其視為java.io.BufferedOutputStream
,除了它可以在消息級別使用。
由於此更改,您必須非常小心,不要在寫完東西后忘記調用ctx.flush()
。或者,您可以直接用writeAndFlush()
。
合理且不易出錯的入站流量掛起
3.x具有由Channel.setReadable(boolean)
提供的不直觀的入站流量掛起機制。它引入了在ChannelHandlers之間的復雜交互操作,如果實現不當,它們很容易相互干擾。
在4.0中,添加了一個名為read()
的新出站操作。如果使用Channel.config().setAutoRead(false)
禁用默認的自動讀取auto-read標志,則在您明確調用該read()
操作之前,Netty不會讀取任何內容。一旦read()
發出的操作完成並且通道再次停止讀取后,將觸發一個名為channelReadSuspended()
的入站事件,以便您可以重新發出另一個read()
操作。您還可以攔截read()
操作以執行更高級的流量控制。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//連接建立后,設置為不自動讀入站流量
ctx.channel().config().setAutoRead(false);
super.channelActive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//這里可根據條件,決定是否再讀一次
ctx.channel().read();
super.channelReadComplete(ctx);
}
暫停接受新連接
用戶沒有辦法告訴Netty 3.x停止接受傳入的連接,除非阻塞I / O線程或關閉服務器套接字。像普通通道一樣,4.0會在read()
未設置自動讀取標志時遵守操作。
半封閉式sockets
TCP和SCTP允許用戶關閉套接字的出站流量,而無需完全關閉它。這樣的套接字稱為“半封閉套接字”,用戶可以通過調用SocketChannel.shutdownOutput()
來創建半封閉套接字。如果遠端關閉了出站流量,SocketChannel.read(..)
則將返回-1
,這似乎與關閉的連接沒有區別。
3.x沒有shutdownOutput()
操作。此外,它總是在SocketChannel.read(..)
返回-1
時關閉連接。
為了支持半封閉的套接字,4.0 新增了SocketChannel.shutdownOutput()
方法,並且用戶可以設置' ALLOW_HALF_CLOSURE
'ChannelOption
以防止Netty即使SocketChannel.read(..)
返回-1
時自動關閉連接。
靈活的I / O線程分配
在3.x中,Channel
由ChannelFactory
創建,並且新創建的Channel
將自動注冊到隱藏的I / O線程。4.0替換ChannelFactory
為一個名為EventLoopGroup
包含一個或多個EventLoop
s的新接口。同樣,新Channel
的不會自動注冊到EventLoopGroup
,而是用戶必須明確地調用EventLoopGroup.register()
。
由於此更改(即,ChannelFactory
線程和I / O線程分離),用戶可以將不同的Channel
實現注冊到相同的EventLoopGroup
,或者將相同的Channel
實現注冊到不同EventLoopGroupS
。例如,您可以在同一I / O線程中運行一個NIO服務器套接字,NIO客戶端套接字,NIO UDP套接字和in-VM本地channels。當編寫需要最小延遲的代理服務器時,這將非常有用。
能夠從現有的JDK套接字創建一個Channel
3.x無法通過現有的JDK套接字(例如java.nio.channels.SocketChannel
)創建新的Channel 。您可以在4.0中做到。
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);
I / O線程上注銷和重新注冊Channel
一旦在3.x中創建了新的Channel
,它將完全綁定到單個I / O線程,直到其底層套接字關閉。在4.0中,用戶可以將Channel
從其I / O線程中注銷 ,以獲得對其底層JDK套接字的完全控制。例如,您可以利用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
管理的EventLoopS
中的一個。EventLoop
實現了java.util.concurrent.ScheduledExecutorService
。這意味着用戶可以執行或調度用戶Channel所屬的I / O線程的任意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() { ... });
}
}
簡化關閉
在4.x里不再有releaseExternalResources()
。您可以立即關閉所有打開的通道,並通過調用EventLoopGroup.shutdownGracefully()
使所有I / O線程停止運行。
類型安全的 ChannelOption
有兩種方法可以配置Netty中的Channel
套接字參數。一種是顯式調用ChannelConfig
的setter ,例如SocketChannelConfig.setTcpNoDelay(true)
。這是最類型安全的方式。另一種是調用ChannelConfig.setOption()
方法。有時,您必須確定在運行時中配置哪些套接字選項,而這種方法在這種情況下是合適的。但是,在3.x中它很容易出錯,因為用戶必須將選項指定為字符串和一個對象的組合對。如果用戶使用錯誤的選項名稱或值,則用戶將遇到ClassCastException
或指定的選項甚至可能被靜默忽略。
4.0引入了一個稱為ChannelOption
的新類型,它提供對套接字選項的類型安全訪問。
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
為了響應用戶需求,您可以將任何對象附加到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();
}
...
}
新的引導程序API
引導bootstrap API已從頭開始重寫,盡管其用途保持不變。它執行使服務器或客戶端啟動並運行所需的典型步驟,通常可在樣板代碼中找到。
新的引導程序還采用了流式接口。
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
正如您在上面的示例中注意到的那樣,ChannelPipelineFactory
不再存在。已將其替換為ChannelInitializer
,從而可以更好地控制Channel
和ChannelPipeline
配置。
請注意,您不是自己創建新的ChannelPipeline
。在查看許多用例之后,Netty項目團隊得出結論,用戶創建自己的管道pipeline 實現或擴展默認實現對用戶沒有好處。因此,ChannelPipeline
不再由用戶創建。ChannelPipeline
是由自動創建的Channel
。
ChannelFuture
→ChannelFuture
和ChannelPromise
ChannelFuture
已被拆分為ChannelFuture
和ChannelPromise
。這不僅使異步操作的使用者和生產者的約定明確,而且使使用鏈中的ChannelFuture
返回值(如過濾)更加安全,因為ChannelFuture
不能更改狀態。
由於此更改,某些方法現在接受ChannelPromise
而不是ChannelFuture
來修改其狀態。
定義明確的線程模型
盡管嘗試修復3.5中的不一致問題,但3.x中沒有明確定義的線程模型。4.0定義了一個嚴格的線程模型,可以幫助用戶編寫ChannelHandler而不用過多擔心線程安全。
-
Netty絕不會 同時調用
ChannelHandler
的方法,除非使用注解@Sharable
標識ChannelHandler
。這與處理程序方法的類型無關-入站,出站或生命周期事件處理程序方法。- 用戶不再需要同步入站或出站事件處理程序方法。
- 4.0不允許
ChannelHandler
多次添加,除非帶有注解@Sharable
。
-
Netty進行的每個
ChannelHandle
方法調用之間總是存在happens-before
關系- 用戶無需定義
volatile
字段即可保留處理程序的狀態。
- 用戶無需定義
-
用戶可以在將處理程序添加到
ChannelPipeline
中時指定一個EventExecutor
。- 如果指定,則的處理程序方法
ChannelHandler
始終由指定的EventExecutor
調用。 - 如果未指定,則處理程序方法始終由與其相關聯
Channel
注冊到的EventLoop
調用。
- 如果指定,則的處理程序方法
-
分配給處理程序或通道的
EventExecutor
和EventLoop
始終是單線程的。- 處理程序方法將始終由同一線程調用。
- 如果指定了多線程
EventExecutor
或EventLoop
,則將首先選擇一個線程,然后將使用所選擇的線程,直到注銷為止。 - 如果在同一管道中的兩個處理程序分配有不同的
EventExecutor
,則將同時調用它們。如果多個處理程序訪問共享數據,即使共享數據僅由同一管道中的多個處理程序訪問,用戶也必須注意線程安全。
-
被添加到
ChannelFuture
的ChannelFutureListeners
總是由分配給與Future
關聯的Channel
的EventLoop
線程調用。 -
ChannelHandlerInvoker
可用於控制Channel
事件的順序。DefaultChannelHandlerInvoker
立即執行該EventLoop
線程中的事件,並作為Runnable
上的對象執行EventExecutor
上其他線程中的事件。請參閱以下示例,以了解與EventLoop
線程中的Channel
和其他線程進行交互時可能產生的含義。(此功能已從此刪除。請參閱相關的提交)
寫順序-混合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
-它包含在核心中。
當您添加ChannelHandler
到一個ChannelPipeline
告訴管道pipeline 總是調用添加的ChannelHandler
的處理方法時,您可以指定EventExecutor
。
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()
的方法簽名進行了些許改變以支持泛型和刪除冗余參數。
編解碼器嵌入→ EmbeddedChannel
編解碼器嵌入器已被替換為io.netty.channel.embedded.EmbeddedChannel
,允許用戶測試包括編解碼器的任何類型的管道。
HTTP編解碼器
現在,HTTP解碼器總是為單個HTTP消息生成多個消息對象:
1 * HttpRequest / HttpResponse
0 - n * HttpContent
1 * LastHttpContent
更多詳細信息,請參閱更新的HttpSnoopServer
示例。如果您不希望為一個HTTP消息處理多個消息,則可以在管道中添加一個HttpObjectAggregator
。HttpObjectAggregator
會將多個消息轉換為一個FullHttpRequest
或FullHttpResponse
。
Transport實現的變化
新添加了以下傳輸:
- OIO SCTP傳輸
- UDT傳輸
案例研究:移植階乘示例
本節顯示了將階乘示例從3.x移植到4.0的粗略步驟。io.netty.example.factorial
軟件包中的階乘示例已移植到4.0 。請瀏覽示例的源代碼以查找每個更改的地方。
移植服務器
- 重寫
FactorialServer.run()
方法以使用新的引導API。 - 移除
ChannelFactory
。 自己實例化NioEventLoopGroup
(一個實例接受傳入的連接,另一個實例處理接受的連接)。 - 重命名
FactorialServerPipelineFactory
為FactorialServerInitializer
。 - 使它繼承
ChannelInitializer<Channel>
。 - 不現主動創建一個新的
ChannelPipeline
,而是通過Channel.pipeline()
獲取。 - 使
FactorialServerHandler
繼承ChannelInboundHandlerAdapter
。 - 替換
channelDisconnected()
為channelInactive()
。 handleUpstream()
不再使用。- 重命名
messageReceived()
成channelRead()
並相應地調整方法簽名。 - 替換
ctx.write()
為ctx.writeAndFlush()
。 - 使
BigIntegerDecoder
繼承ByteToMessageDecoder<BigInteger>
。 - 使
NumberEncoder
繼承MessageToByteEncoder<Number>
。 encode()
不再返回緩沖區。將編碼的數據填充到ByteToMessageDecoder
提供的緩沖區中。
移植客戶端
與移植服務器大致相同,但是在編寫潛在的大數據流時需要注意。
- 重寫
FactorialClient.run()
方法以使用新的引導API。 - 重命名
FactorialClientPipelineFactory
為FactorialClientInitializer
。 - 使
FactorialClientHandler
繼承ChannelInboundHandlerAdapter