New and noteworthy - Netty

标签: 服务器nettyjava
4338人阅读 评论(0) 收藏 举报

原文链接 http://netty.io/wiki/new-and-noteworthy.html

This document walks you through the list of notable changes and new features in the major Netty release to give you an idea to port your application to the new version.



The package name of Netty has been changed from org.jboss.netty toio.netty since we are not part of JBoss.org anymore.

The binary JAR has been split into multiple submodules so that a user can exclude unnecessary features from the class path. The current structure looks like this following:

Netty的包名从org.jboss.netty 改为io.netty,我们已不再是JBoss.org的一部分了。


Artifact ID Description
netty-parent Maven parent POM
netty-common Utility classes and logging facade
netty-buffer ByteBuf API that replaces java.nio.ByteBuffer
netty-transport Channel API and core transports
netty-transport-rxtx Rxtx transport
netty-transport-sctp SCTP transport
netty-transport-udt UDT transport
netty-handler Useful ChannelHandler implementations
netty-codec Codec framework that helps write an encoder and a decoder
netty-codec-http Codecs related with HTTP, Web Sockets, SPDY, and RTSP
netty-codec-socks Codecs related with SOCKS protocol
netty-all All-in-one JAR that combines all artifacts above
netty-tarball Tarball distribution
netty-example Examples
netty-testsuite-* A collection of integration tests
netty-microbench Microbenchmarks


  • Most operations in Netty now support method chaining for brevity.
  • Non-configuration getters have no get- prefix anymore. (e.g. Channel.getRemoteAddress()Channel.remoteAddress())
    • Boolean properties are still prefixed with is- to avoid confusion (e.g. 'empty' is both an article and a verb, soempty() can have two meanings.)
  • For API changes between 4.0 CR4 and 4.0 CR5 see Netty 4.0.0.CR5 released with new-new API

  • 大多数的操作都采用简短的链接
  • getters方法不在以get开头(例如 Channel.getRemoteAddress()Channel.remoteAddress())。Boolean相关的属性仍然以is开头以防止造成混淆
  • 对于4.0的RC4和RC5之间的差别请参考http://netty.io/news/2013/06/18/4-0-0-CR5.html

Buffer API更改


Thanks to the structural changes mentioned above, the buffer API can be used as a separate package. Even if you are not interested in adopting Netty as a network application framework, you can still use our buffer API. Therefore, the type name ChannelBuffer does not make sense anymore, and has been renamed to ByteBuf.

The utility class ChannelBuffers, which creates a new buffer, has been split into two utility classes,Unpooled and ByteBufUtil. As can be guessed from its name Unpooled, 4.0 introduced pooledByteBufs which can be allocated via ByteBufAllocator implementations.

上面介绍的模块化的更改,我们可以单独的使用buffer API。即使你不打算用Netty来开发网络程序,你仍然可以使用buffer API。因此我们不在使用ChannelBuffer来命名而是采用ByteBuf。

ChannelBuffer 中的类被分解为UnpooledByteBufUtil两个工具类,通过Unpooled就能猜到,4.0引入一个新的pooledByteBuf,通过ByteBufAllocator来实现ByteBuf的实现。

ByteBuf 不是一个接口而是一个抽象类

According to our internal performance test, converting ByteBuf from an interface to an abstract class improved the overall throughput around 5%.



In 3.x, buffers were fixed or dynamic. The capacity of a fixed buffer does not change once it is created while the capacity of a dynamic buffer changes whenever its write*(...) method requires more space.

Since 4.0, all buffers are dynamic. However, they are better than the old dynamic buffers. You can decrease or increase the capacity of a buffer more easily and more safely. It's easy because there is a new method ByteBuf.capacity(int newCapacity). It's safe because you can set the maximum capacity of a buffer so that it does not grow boundlessly.


从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.

// Decrease the capacity of the buffer (the last 512 bytes are deleted.)
The only exception is the buffer which wraps a single buffer or a single byte array, created by wrappedBuffer(). You cannot increase its capacity because it invalidates the whole point of wrapping an existing buffer - saving memory copies. If you want to change the capacity after you wrap a buffer, you should just create a new buffer with enough capacity and copy the buffer you wanted to wrap.



A new buffer implementation named CompositeByteBuf defines various advanced operations for composite buffer implementations. A user can save bulk memory copy operations using a composite buffer at the cost of relatively expensive random access. To create a new composite buffer, use either Unpooled.wrappedBuffer(...) like before,Unpooled.compositeBuffer(...), or ByteBufAllocator.compositeBuffer().

CompositeByteBuf定义了很多高级和复杂的buffer操作的实现。用户可以用composite buffer操作整块内存,这样比零碎的内存访问要搞笑很多。要创建一个新的composite buffer 可以使用Unpooled.wrappedBuffer(...) ,Unpooled.compositeBuffer(...) 或者ByteBufAllocator.compositeBuffer()

Predictable NIO buffer conversion

The contract of ChannelBuffer.toByteBuffer() and its variants were not deterministic enough in 3.x. It was impossible for a user to know if they would return a view buffer with shared data or a copied buffer with separate data. 4.0 replaces to ByteBuffer() with ByteBuf.nioBufferCount(), nioBuffer(), and nioBuffers(). If nioBufferCount() returns0, a user can always get a copied buffer by calling copy().nioBuffer().


新的toByteBuffer()方法不再使用ByteBuf.nioBufferCount(), nioBuffer()nioBuffers()方法。如果nioBufferCount()返回0,则用户应该调用copy().nioBuffer()来获取copied buffer。

Little endian support changes

Little endian support has been changed significantly. Previously, a user was supposed to specify a LittleEndianHeapChannelBufferFactory or wrap an existing buffer with the desired byte order to get a little endian buffer. 4.0 adds a new method:ByteBuf.order(ByteOrder). It returns a view of the callee with the desired byte order:


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);

Pooled buffers

Netty 4 introduces a high-performance buffer pool which is a variant of jemalloc that combines buddy allocation and slab allocation. It gives the following benefits:

  • Reduced GC pressure incurred by frequent allocation and deallocation of the buffers
  • Reduced memory bandwidth consumption incurred when creating a new buffer which inevitably has to be filled with zeroes
  • Timely deallocation of direct buffers

To take advantage of this feature, unless a user wants to get an unpooled buffer, he or she should get a buffer from a ByteBufAllocator:

Netty4 引入一个高度优化的缓冲池,和jemalloc 不同,jemalloc 依赖于buddy allocation slab allocation。使用缓存池有以下几点好处:

  • 不用频繁的申请和释放内存以减轻GC的压力。
  • 不需要再考虑新申请buffer时将其置零以减轻内存带宽的压力。
  • 可以及时而直接的进行缓存分配

上面介绍了这么多好处,除非用户还是想用unpooled buffer。否则他们应该使用ByteBufAllocator来创建新的缓存:

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

Once a ByteBuf is written to the remote peer it will be returned automatically to the pool it originated from.

The default ByteBufAllocator is PooledByteBufAllocator. If you do not wish to use buffer pooling or use your own allocator, use Channel.config().setAllocator(...) with an alternative allocator such as UnpooledByteBufAllocator.

NOTE: At the moment, the default allocator is UnpooledByteBufAllocator. Once we ensure there's no memory leak inPooledByteBufAllocator, we will default back again to it.



注意: 这种情况下allocator是UnpooledByteBufAllocator。一旦我们想确保没有内存泄露,我们应该再次回来使用PooledByteBufAllocator

ByteBuf is always reference counted

To control the life cycle of a ByteBuf in a more predictable way, Netty does not rely on the garbage collector anymore but employs an explicit reference counter. Here's the basic rule:

  • When a buffer is allocated, its initial reference count is 1.
  • If the reference count of the buffer is decreased to 0, it is deallocated or returned to the pool it originated from.
  • The following attempts trigger an IllegalReferenceCountException:
    • Accessing a buffer whose reference count is 0,
    • Decreasing the reference count to a negative value, or
    • Increasing the reference count beyond Integer.MAX_VALUE.
  • Derived buffers (e.g. slices and duplicates) and swapped buffers (i.e. little endian buffers) share the reference count with the buffer it was derived from. Note that the reference count does not change when a derived buffer is created.
  • 当分配一个buffer,其初始引用计数为1
  • 当一个buffer的引用计数为0,它将会被缓存池回收。
  • 以下几种情况将会引发IllegalReferenceCountException异常:1. 访问一个引用计数为0的buffer。2. 引用计数减为负数。3.引用计数的增加超过Integer.MAX_VALUE
  • Derived buffers(切片,副本)和swapped buffers(小字节序)将和原始的buffer共享引用计数。当创建一个derived buffer时并不会改变引用计数器。

When a ByteBuf is used in a ChannelPipeline, there are additional rules you need to keep in mind:

  • Each inbound (a.k.a. upstream) handler in a pipeline has to release the received messages. Netty does not release them automatically for you.
    • Note that codec framework does release the messages automatically and a user has to increase the reference count if he or she wants to pass a message as-is to the next handler.
  • When an outbound (a.k.a. downstream) message reaches at the beginning of the pipeline, Netty will release it after writing it out.
  • 对于inbound (a.k.a. upstream) handler, 需要手动释放收到的消息,Netty不会自动帮你释放。注意:codec framework 会自动释放消息,如果用户要将消息传给其它handler需要增加其引用计数。
  • 对于outbound (a.k.a. downstream)消息,Netty在将其发送出去后释放它;无需用户手动释放。

Automatic buffer leak detection

Although reference counting is very powerful, it is also error-prone. To help a user find where he or she forgot to release the buffers, the leak detector logs the stack trace of the location where the leaked buffer was allocated automatically.

Because the leak detector relies on PhantomReference and obtaining a stack trace is a very expensive operation, it samples approximately 1% of allocations only. Therefore, it's a good idea to run the application for a reasonably long time to find all possible leaks.

Once all leaks are found and fixed. You can turn this feature off to remove its runtime overhead completely by specifying the -Dio.netty.noResourceLeakDetection JVM option.





Along with the new standalone buffer API, 4.0 provides various constructs which are useful for writing asynchronous applications in general at the new package called io.netty.util.concurrent. Some of those constructs are:

  • Future and Promise - similar to ChannelFuture, but has no dependency  to Channel
  • EventExecutor and EventExecutorGroup - generic event loop API

They are used as the base of the channel API which will be explained later in this document. For example,ChannelFuture  extends io.netty.util.concurrent.Future and EventLoopGroup extends EventExecutorGroup.

由于采用新的独立的buffer API, 4.0 提供了io.netty.util.concurrent包来开发异步程序。其中的一些构造方式如下:
  • FuturePromise和ChannelFuture相似,但是不在依赖于Channel
  • EventExecutor , EventExecutorGroup -常用的消息循环处理API

Channel API changes

In 4.0, many classes under the io.netty.channel package have gone through a major overhaul, and thus simple text search-and-replace will not make your 3.x application work with 4.0. This section will try to show the thought process behind such a big change, rather than being an exhaustive resource for all the changes.

4.0中,io.netty.channel包中的很多类都经过了重大修改。简单的文本替换不能从3.x切换到4.0 。本节将讨论需要注意的重大更改。

Revamped ChannelHandler interface

Upstream → Inbound, Downstream → Outbound

The terms 'upstream' and 'downstream' were pretty confusing to beginners. 4.0 uses 'inbound' and 'outbound' wherever possible.


New ChannelHandler type hierarchy

In 3.x, ChannelHandler was just a tag interface, and ChannelUpstreamHandler,ChannelDownstreamHandler, and LifeCycleAwareChannelHandler defined the actual handler methods. In Netty 4,ChannelHandler merges LifeCycleAwareChannelHandler along with a couple more methods which are useful to both an inbound and an outbound handler:

在3.x中ChannelHandler只是一个接口,ChannelUpstreamHandler, ChannelDownstreamHandler 和  LifeCycleAwareChannelHandler才定义了实际的方法。在4.0中,ChannelHandlerLifeCycleAwareChannelHandler中很多关于inbound,outbound有用的方法合并在了一起:

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

The following diagram depicts the new type hierarchy:


ChannelHandler with no event object

In 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.


4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() and handleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

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 has also been changed to reflect the changes mentioned above:


// Before:
// After:
All these changes mean a user cannot extend the non-existing ChannelEvent interface anymore. How then does a user define his or her own event type such as IdleStateEvent?ChannelInboundHandler in 4.0 has a handler method called userEventTriggered() which is dedicated to this specific user case.


Simplified channel state model

When a new connected Channel is created in 3.x, at least three ChannelStateEvents are triggered: channelOpen, channelBound, and channelConnected. When a Channel is closed, at least 3 more:channelDisconnected,channelUnbound, and channelClosed.

在3.x中,当一个新的连接建立时,至少会有3个ChannelStateEvents被触发:channelOpen,channelBound, andchannelConnected。当一个channel关闭还会触发3个事件:channelDisconnected,channelUnbound, andchannelClosed

However, it's of dubious value to trigger that many events. It is more useful for a user to get notified when a Channel enters the state where it can perform reads and writes.


channelOpen, channelBound, and channelConnected have been merged to channelActive.channelDisconnected,channelUnbound, and channelClosed have been merged to channelInactive. Likewise,Channel.isBound() and isConnected() have been merged to isActive().

Note that channelRegistered and channelUnregistered are not equivalent to channelOpen and channelClosed. They are new states introduced to support dynamic registration, deregistration, and re-registration of a Channel, as illustrated below:

channelOpen, channelBound, and channelConnected已经被合并为一个方法channelActivechannelDisconnected,channelUnbound, andchannelClosed也被合并到一个方法中channelInactive。同样,Channel.isBound() andisConnected()被合并为isActive()

注意,channelRegistered and channelUnregistered并不等同于channelOpen andchannelClosed。这是两个新引入的状态用来动态注册和反注册channel,也可以再次注册一个Channel.

write() does not flush automatically

4.0 introduced a new operation called flush() which explicitly flushes the outbound buffer of aChannel, and write() operation does not flush automatically. You can think of this as a java.io.BufferedOutputStream, except that it works at message level.

Because of this change, you must be very careful not to forget to call ctx.flush() after writing something. Alternatively, you could use a shortcut method writeAndFlush().



Sensible and less error-prone inbound traffic suspension

3.x had an unintuitive inbound traffic suspension mechanism provided by Channel.setReadable(boolean). It introduced complicated interactions between ChannelHandlers and the handlers were easy to interfere with each other if implemented incorrectly.

In 4.0, a new outbound operation called read() has been added. If you turn off the default auto-read flag withChannel.config().setAutoRead(false), Netty will not read anything until you explicitly invoke the read() operation. Once the read() operation you issue is complete and the channel again stops reading, an inbound event called channelReadSuspended() will be triggered so that you can re-issue another read() operation. You can also intercept a read() operation to perform more advanced traffic control.



Suspension of accepting incoming connections

There was no way for a user to tell Netty 3.x to stop accepting incoming connections except for blocking the I/O thread or closing the server socket. 4.0 respects the read() operation when the auto-read flag is not set, just like an ordinary channel.


Half-closed sockets

TCP and SCTP allow a user to shut down the outbound traffic of a socket without closing it completely. Such a socket is called 'a half-closed socket', and a user can make a half-closed socket by calling SocketChannel.shutdownOutput() method. If a remote peer shuts down the outbound traffic,SocketChannel.read(..) will return-1, which was seemingly indistinguishable from a closed connection.

TCP和SCTP允许用户在不关闭socket的情况停止向外发送数据。这类socket被称为'a half-closed socket。用户可以调用SocketChannel.shutdownOutput()方法实现half-closed socket。如果对端关闭了outbound, SocketChannel.read()将返回-1,看上去像对端关闭了链接。

3.x did not have shutdownOutput() operation. Also, it always closed the connection when SocketChannel.read(..) returns -1.

To support a half-closed socket, 4.0 adds SocketChannel.shutdownOutput() method, and a user can set the 'ALLOW_HALF_CLOSURE' ChannelOption to prevent Netty from closing the connection automatically even if SocketChannel.read(..) returns -1.



Flexible I/O thread allocation

In 3.x, a Channel is created by a ChannelFactory and the newly created Channel is automatically registered to a hidden I/O thread. 4.0 replaces ChannelFactory with a new interface called EventLoopGroup which consists of one or moreEventLoops. Also, a newChannel is not registered to the EventLoopGroup automatically but a user has to call EventLoopGroup.register() explicitly.

Thanks to this change (i.e. separation of ChannelFactory and I/O threads), a user can register different Channel implementations to the same EventLoopGroup, or same Channel implementations to different EventLoopGroups. For example, you can run a NIO server socket, NIO client sockets, NIO UDP sockets, and in-VM local channels in the same I/O thread. It should be very useful when writing a proxy server which requires minimal latency.


由于上面说的更改,用户可以将不同的Channel注册到一个EventLoopGroup上,或者将一个Channel注册到不同的EventLoopGroup上。例如,你可以在一个I/O线程上同时运行NIO server socket, NIO client sockets, NIO UDP sockets, and in-VM local channels。这种方式对于开发低延迟的代理服务器很有帮助。

Ability to create a Channel from an existing JDK socket

3.x provided no way to create a new Channel from an existing JDK socket such as java.nio.channels.SocketChannel. You can with 4.0.

3.x中不能从已知的JDK socket上创建Channel,但是4.0可以。

Deregistration and re-registration of a Channel from·to an I/O thread

Once a new Channel is created in 3.x, it is completely tied to a single I/O thread until its underlying socket is closed. In 4.0, a user can deregister a Channel from its I/O thread to gain the full control of its underlying JDK socket. For example, you can take advantage of high-level non-blocking I/O Netty provides to deal with complex protocols, and then later deregister the Channel and switch to blocking mode to transfer a file at possible maximum throughput. Of course, it is possible to register the deregistered Channel back again.

在3.x中一旦新的Channel被创建,它将一直被绑定在一个I/O线程上直到它关闭为止。在4.0中,用户可以从I/O线程上注销一个Channel来获得对JDK socket的完全控制。例如,你可以采用一个更高优先级的I/O线程来处理复杂的协议交互,处理完成后可以注销相关的Channel并切换到阻塞模式来处理大文件的传输。当然,你也可以再次注册以前注销过的Channel到你的I/O线程上。

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 = ...;
// Deregister from Netty.
// Perform some blocking operation here.
myFile.transferFrom(mySocket, ...);
// Register back again to another event loop group.
EventLoopGroup anotherGroup = ...;

Scheduling an arbitrary task to be run by an I/O thread

When a Channel is registered to an EventLoopGroup, the Channel is actually registered to one of the EventLoops which is managed by the EventLoopGroup.EventLoop implements java.util.concurrent.ScheduledExecutorService. It means a user can execute or schedule an arbitrary Runnable or Callable in an I/O thread where the user's channel belongs to. Along with the new well-defined thread model, which will be explained later, it became extremely easier to write a thread-safe handler.


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() { ... });

Simplified shutdown

There's no more release ExternalResources(). You can close all open channels immediately and make all I/O threads stop themselves by calling EventLoopGroup.shutdownGracefully().


Type-safe ChannelOption

There are two ways to configure the socket parameters of a Channel in Netty. One is to call the setters of a ChannelConfig explicitly, such as SocketChannelConfig.setTcpNoDelay(true). It is the most type-safe way. The other is to call ChannelConfig.setOption() method. Sometimes you have to determine what socket options to configure in runtime, and this method is ideal for such cases. However, it is error-prone in 3.x because a user has to specify the option as a pair of a string and an object. If a user calls with the wrong option name or value, he or she will encounter a ClassCastException or the specified option might even be ignored silently.

4.0 introduces a new type called ChannelOption, which provides type-safe access to socket options.

在Netty中有两种方式来配置socket的参数。一种方式是直接调用ChannelConfig的setter方法,例如SocketChannelConfig.setTcpNoDelay(true)方法,这是最类型安全的方式。另一种方式调用ChannelConfig.setOption()方法。有时候,你需要在程序运行期间配置socket option,就得采用第二种方式来处理。在3.x中,用户采用字符串的方式来设置相关的option,所以很容易出错。如果不小心将相关的option的名字或值填错,就会引发ClassCastException异常或设置不能生效。

4.0引入一个新类ChannelOption来提供类型安全的socket option。

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


In response to user demand, you can attach any object to Channel and ChannelHandlerContext. A new interface called AttributeMap, which Channel and ChannelHandlerContext extend, has been added. Instead,ChannelLocal and Channel.attachment are removed. The attributes are garbage-collected when their associated Channel is garbage-collected.

为了能够正确的处理响应,你可以向Channel and ChannelHandlerContext附加任何对象。Channel andChannelHandlerContext实现了一个新的接口AttributeMap,移除了ChannelLocal andChannel.attachment。当Channel被回收时,附加到它上面的属性也会被回收。

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

New bootstrap API

The bootstrap API has been rewritten from scratch although its purpose stays same; it performs the typical steps required to make a server or a client up and running, often found in boilerplate code.

The new bootstrap also employs a fluent interface.

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)
         .option(ChannelOption.SO_BACKLOG, 100)
         .childOption(ChannelOption.TCP_NODELAY, true)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             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.
    } finally {
        // Shut down all event loops to terminate all threads.
        // Wait until all threads are terminated.


As you noticed in the example above, there is no ChannelPipelineFactory anymore. It has been replaced with ChannelInitializer, which gives more control over Channel and ChannelPipeline configuration.

Please note that you don't create a new ChannelPipeline by yourself. After observing many use cases reported so far, the Netty project team concluded that it has no benefit for a user to create his or her own pipeline implementation or to extend the default implementation. Therefore, ChannelPipeline is not created by a user anymore.ChannelPipeline is automatically created by aChannel.

上面的示例中没有找到ChannelPipelineFactory,我们已经需要这个类而采用新的类 ChannelInitializer,可以让我们对Channel和ChannelPipeline有更多的控制权。


ChannelFutureChannelFuture and ChannelPromise

ChannelFuture has been split into ChannelFuture and ChannelPromise. This not only makes the contract of consumer and producer of an asynchronous operation explicit, but also makes it more safe to use the returned ChannelFuture in a chain (like filtering), because the state of the ChannelFuture cannot be changed.

Due to this change, some methods now accept ChannelPromise rather than ChannelFuture to modify its state.



Well-defined thread model

There is no well-defined thread model in 3.x although there was an attempt to fix its inconsistency in 3.5. 4.0 defines a strict thread model that helps a user write a ChannelHandler without worrying too much about thread safety.

  • Netty will never call a ChannelHandler's methods concurrently, unless the ChannelHandler is annotated with @Shareable. This is regardless of the type of handler methods - inbound, outbound, or life cycle event handler methods.
    • A user does not need to synchronize either inbound or outbound event handler methods anymore.
    • 4.0 disallows adding a ChannelHandler more than once unless it's annotated with @Sharable.
  • There is always happens-before relationship between each ChannelHandler method invocations made by Netty.
    • A user does not need to define a volatile field to keep the state of a handler.
  • A user can specify an EventExecutor when he or she adds a handler to a ChannelPipeline.
    • If specified, the handler methods of the ChannelHandler are always invoked by the specified EventExecutor.
    • If unspecified, the handler methods are always invoked by the EventLoop that its associated Channel is registered to.
  • EventExecutor and EventLoop assigned to a handler or a channel are always single-threaded.
    • The handler methods will always be invoked by the same thread.
    • If multithreaded EventExecutor or EventLoop is specified, one of the threads will be chosen first and then the chosen thread will be used until deregistration.
    • If two handlers in the same pipeline are assigned with different EventExecutors, they are invoked simultaneously. A user has to pay attention to thread safety if more than one handler access shared data even if the shared data is accessed only by the handlers in the same pipeline.
  • The ChannelFutureListeners added to ChannelFuture are always invoked by the EventLoop thread assigned to the future's associated Channel.
  • Netty绝不会并发的调用ChannelHandler的方法,除非该ChannelHandler被标记为@shareable。这条规则适用于handler的所有方法。用户无需考虑inbound, outbound相关方法的同步问题。4.0不允许多次添加同一个ChannelHandler,当然被标记为@Sharable的除外。
  • ChannelHandler的每个方法调用都遵循happens-before关系。用户无需使用volatile字段来保持handler的状态。
  • 当用户要将handler附加到ChannelPipeline上时可以指定一个EventExecutor。如果指定了EventExecutor,那么ChannelHandler的所有方法都会运行在指定的EventExecutor上。如果没有指定,ChannelHandler的方法将会运行在注册到Channel上时被分配的EventLoop上面。
  • EventExecutor 和 EventLoop会指定一个handler上,或者channel总是运行在一个单独的线程上。 handler的所有方法总是运行在同一个线程上。如果指定了EventExecutorEventLoop,那么首次被指定到Channel的线程将会一直伴随该Channel直到它从线程池中注销。如果一个pipeline中的两个handler被指定到2各不同的EventExecutor,那么他们将会同时运行。用户需要自己处理多个handler访问共享数据的线程安全问题。
  • 添加到ChannelFuture上的ChannelFutureListener总是在Channel分配的线程中执行。

No more ExecutionHandler - it's in the core.

You can specify an EventExecutor when you add a ChannelHandler to a ChannelPipeline to tell the pipeline to always invoke the handler methods of the added ChannelHandler via the specified 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());

Codec framework changes

There were substantial internal changes in the codec framework because 4.0 requires a handler to create and manage its buffer (see Per-handler buffer section in this document.) However, the changes from a user's perspective are not very big.

  • Core codec classes are moved to the io.netty.handler.codec package.
  • FrameDecoder has been renamed to ByteToMessageDecoder.
  • OneToOneEncoder and OneToOneDecoder were replaced with MessageToMessageEncoder and MessageToMessageDecoder.
  • The method signatures of decode(), decodeLast(), encode() were changed slightly to support generics and to remove redundant parameters.
  • 核心codec 类移到io.netty.handler.codec包中。
  • FrameDecoder被重命名为ByteToMessageDecoder.
  • OneToOneEncoderOneToOneDecoder被MessageToMessageEncoderMessageToMessageDecoder所替代。
  • decode(), decodeLast(), encode()这几个方法删除了多余的参数并且添加了泛型支持。

Codec embedder → EmbeddedChannel

Codec embedder has been replaced by io.netty.channel.embedded.EmbeddedChannel to allow a user to test any kind of pipeline including a codec.

Codec embedder被io.netty.channel.embedded.EmbeddedChannel所替代,以运行用户测试某个pipeline是否包含指定的codec。

HTTP codec

HTTP decoders now always generates multiple message objects per a single HTTP message:

HTTP decoders现在可以将多个消息对象放在一条HTTP消息中:

1       * HttpRequest / HttpResponse
0 - n   * HttpContent
1       * LastHttpContent
For more detail, please refer to the updated HttpSnoopServer example. If you wish not to deal with multiple messages for a single HTTP message, you can put an HttpObjectAggregator in the pipeline.HttpObjectAggregator will transform multiple messages into a single Full HttpRequest or Full HttpResponse.

Changes in transport implementations

The following transports were newly added:


  • OIO SCTP transport
  • UDT transport

Case study: porting the Factorial example

This section shows rough steps to port the Factorial example from 3.x to 4.0. The Factorial example has been ported to 4.0 already in theio.netty.example.factorial package. Please browse the source code of the example to find every bits changed.


Porting the server

  1. Rewrite FactorialServer.run() method to use the new bootstrap API.
    1. No ChannelFactory anymore. Instantiate NioEventLoopGroup (one for accepting incoming connections and the other for handling the accepted connections) by yourself.
  2. Rename FactorialServerPipelineFactory to FactorialServerInitializer.
    1. Make it extends ChannelInitializer<Channel>.
    2. Instead of creating a new ChannelPipeline, get it via Channel.pipeline().
  3. Make FactorialServerHandler extends ChannelInboundHandlerAdapter.
    1. Replace channelDisconnected() with channelInactive().
    2. handleUpstream() is not used anymore.
    3. Rename messageReceived() into channelRead() and adjust the method signature accordingly.
    4. Replace ctx.write() with ctx.writeAndFlush().
  4. Make BigIntegerDecoder extend ByteToMessageDecoder<BigInteger>.
  5. Make NumberEncoder extend MessageToByteEncoder<Number>.
    1. encode() does not return a buffer anymore. Fill the encoded data to the buffer provided by ByteToMessageDecoder.
  1. 用新的bootstrap API重写了FactorialServer.run()方法。
    1.  不再使用ChannelFactory, 可以通过自己创建NioEventLoopGroup类实例来接受连接并且管理相关的连接。
  2. FactorialServerPipelineFactory重命名为ChannelInboundHandlerAdapter
  3. 确保FactorialServerHandler继承自ChannelInboundHandlerAdapter。用channelInactive()替换channelDisconnected()。handleUpstream()不再使用。messageReceived()被重命名为channelRead()。采用ctx.writeAndFlush()替换ctx.write()
  4. BigIntegerDecoder继承自ByteToMessageDecoder<BigInteger>
  5. NumberEncoder继承自MessageToByteEncoder<Number>encode()不再返回一个buffer。

Porting the client

Mostly same with porting the server, but you need to pay attention when you write a potentially large stream.

  1. Rewrite FactorialClient.run() method to use the new bootstrap API.
  2. Rename FactorialClientPipelineFactory to FactorialClientInitializer.
  3. Make FactorialClientHandler extends ChannelInboundHandlerAdapter


  1. 需要用新的bootstrap API来重写FactorialClient.run()方法。
  2. FactorialClientPipelineFactory重命名为FactorialClientInitializer
  3. FactorialClientHandler继承自ChannelInboundHandlerAdapter


* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    • 访问:141134次
    • 积分:1370
    • 等级:
    • 排名:千里之外
    • 原创:15篇
    • 转载:1篇
    • 译文:7篇
    • 评论:33条