此文档涵盖了此次Netty大版本中值得关注的变更点及新特性,以帮助你将自己的应用顺利移植到新版本。
项目结构变更
基于netty已经不再是JBoss.org的一部分,我们将包名从 org.jboss.netty 变更为 io.netty。
二进制jar包也被分割成了多个子模块,以便用户可以排除非必要的特性。当前结构如下:
| Artifact ID | 描述 | 
|---|---|
| netty-parent | Maven parent POM | 
| netty-common | 工具类及日志接口 | 
| netty-buffer | ByteBufAPI,用来替换java.nio.ByteBuffer | 
| netty-transport | Channel API 及核心 transports | 
| netty-transport-rxtx | Rxtx transport | 
| netty-transport-sctp | SCTP transport | 
| netty-transport-udt | UDT transport | 
| netty-handler | ChannelHandler的相关实现 | 
| netty-codec | 编解码框架,用于编写encoder及decoder | 
| netty-codec-http | HTTP, Web Sockets, SPDY, and RTSP相关的编解码器 | 
| netty-codec-socks | SOCKS协议相关的编解码器 | 
| netty-all | 包含以上所有artifacts的All-in-one的JAR | 
| netty-tarball | Tarball distribution | 
| netty-example | 样例 | 
| netty-testsuite-* | 整合的测试集 | 
| netty-microbench | 微基准测试(Microbenchmarks) | 
所有的artifacts(除了netty-all.jar)都已经是OSGi bundles了,可以直接在你的OSGi容器中使用。
通用API变更
- 为使代码更加简洁,Netty在大多数场景都已经支持链式方法。
- 非可配置变量的getter不再有get-前缀。(如:Channel.getRemoteAddress()→Channel.remoteAddress())- Boolean类型的属性仍然以 is-开头以避免混淆(如: ‘empty’既是一个形容词也是一个动词,那么empty()就会包含两种含义)
 
- Boolean类型的属性仍然以 
- 4.0 CR4与4.0 CR5之间的API变更,请参照 Netty 4.0.0.CR5 released with new-new API
Buffer API变更
ChannelBuffer → ByteBuf
在对netty包结构进行如上调整之后,buffer API也可以作为独立包使用了。所以,即使你不把Netty用来作为网络框架,你仍然可以使用buffer API。因此,ChannelBuffer 这个名字也变得不合时宜,我们便将之重命名为 ByteBuf。
用于创建buffer的工具类 ChannelBuffers 现在被拆分为 Unpooled 及 ByteBufUtil 两个工具类。一如其名,4.0引入了池化的 ByteBuf,可以使用 ByteBufAllocator 的实现类进行分配。
ByteBuf 是抽象类而非接口
根据我们的内部性能测试,将接口 ByteBuf 变更为抽象类,能带来约5%的吞吐量提升。
大部分buffer的最大容量都是动态的
3.x版本,buffer有定长(fixed)和动态(dynamic)两种。定长的buffer一旦创建,它的容量就不会再变化。而动态的buffer在每次操作 write*(…) 时都会根据需要动态调整容量。
4.0开始,所有的buffer都是动态的。并且比旧的动态buffer更加优秀。你可以更加容易和安全的增减buffer的容量。容易是因为提供了新方法 ByteBuf.capacity(int newCapacity)。而安全则是因为你可以设定buffer的最大容量从而防止其无限扩增。
// 不再使用 dynamicBuffer() - 换为 buffer(). ByteBuf buf = Unpooled.buffer(); // 增加buffer容量 buf.capacity(1024); ... // 减少buffer容量(最后512字节会被删除) buf.capacity(512);
通过 wrappedBuffer() 创建的包装了单个buffer或者单个byte数组的buffer是唯一的例外。如果增大其容量就会破坏它包装已存在buffer的意义——节省内存(saving memory copies)。如果你包装了一个buffer以后还想改变它的容量,那么你需要新建一个拥有足够容量的buffer,并且copy你需要包装的部分过去。
新的buffer类型: CompositeByteBuf
新的buffer实现类 CompositeByteBuf 为复合buffer定义了很多高级操作。使用复合buffer可以在相对昂贵的随机访问操作中节省大量的内存复制操作。可跟以前一样使用 Unpooled.wrappedBuffer(…) 新建一个复合buffer,或者使用 Unpooled.compositeBuffer(…) 及 ByteBufAllocator.compositeBuffer() 进行创建。
可预期的NIO buffer转换
3.x版本,ChannelBuffer.toByteBuffer() 及其变体的约定(contract)都不是很清晰。用户并不知道它返回的buffer含有共享数据的视图还是独立的数据副本。4.0版本将 toByteBuffer() 替换为 ByteBuf.nioBufferCount(), nioBuffer() 及nioBuffers()。如果 nioBufferCount() 返回了 0,意味着用户总是可以通过调用 copy().nioBuffer() 拿到一份buffer副本。
对小端字节序(Little endian)支持的变更
小端字节序的支持进行了较大的更改。以前版本,用户可使用 LittleEndianHeapChannelBufferFactory 或者按目标字节序包装一个已存在的buffer来获取一个小端字节序的buffer。4.0新增了一个方法:ByteBuf.order(ByteOrder)。其返回原buffer的目标字节序的视图。
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);池化buffer
Netty4引入了一种高效缓存池(buffer pool),它是结合了 buddy allocation 以及 slab allocation 的 jemalloc 的变体。
- 减小buffer的频繁分配及回收导致的GC压力
- 减少新建buffer时0值填充产生的内存带宽消耗
- 及时回收直接内存(direct buffers)
为了可以利用这些特性,用户都应该使用 ByteBufAllocator 获取buffer,除非你希望使用非池化buffer:
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 写入远程节点(remote peer),就立即自动归还原缓存池。
默认的 ByteBufAllocator 为 PooledByteBufAllocator。如果你不想用缓存池或者希望使用自己的allocator,那么使用 Channel.config().setAllocator(…) 设置你自己的allocator,比如 UnpooledByteBufAllocator。
注意:目前(译者注:这里指此文档发布时)默认的allocator是 UnpooledByteBufAllocator。一旦我们确认 PooledByteBufAllocator 没有内存泄露问题,我们会重新将其设为默认值。
ByteBuf 会一直被被引用计数
为了使 ByteBuf 的生命周期更可控,Netty引入了显式的引用计数,而不再依赖GC了。
- buffer被分配时,它的初始引用计数为1
- 当buffer的引用计数降为0时,会被回收或者归还到池里
- 以下行为会试图触发 IllegalReferenceCountException:- 访问引用计数为0的buffer
- 引用计数降为负,或者
- 引用计数超过 Integer.MAX_VALUE
 
- 衍生buffer(如:slices及duplicates)及交换buffer(如:little endian buffers)共享其来源buffer的引用计数。需注意,衍生buffer创建时,引用计数不会变化。
在 ChannelPipeline 中使用 ByteBuf 时,还需要注意一些额外规则:
- pipeline中的入站(又名:上行)(译者注:原文为inbound及upstream)handler需要手动释放接受到的消息。Netty不会自动进行释放。- 注意,编解码器框架会自动释放消息,如果用户想要把消息原样传递到下一个handler中,那就必须手动增加引用计数。
 
- 当出站(又名:下行)(译者注:原文为outbound及downstream)消息到达pipline的起始点,Netty会在写出之后进行释放
自动buffer泄露检测
尽管引用计数已经很强大了,可同时也很容易出错。泄露探测器(leak detector)会记录buffer自动分配时的栈轨迹信息,协助用户排查忘记释放buffer的问题。
由于泄露探测器使用了 PhantomReference,并且获取栈轨迹信息成本很高,它仅进行了1%分配的采样。因此,应该让应用运行足够长的时间来查找所有可能的泄露问题。
一旦所有的泄露问题都找到并解决了,就可以通过指定JVM参数 -Dio.netty.noResourceLeakDetection 来关掉此特性,以此消除运行时的额外开销。
io.netty.util.concurrent
随着buffer API的独立化,4.0也提供了很多通用的用于异步应用的组件,并且新包命名为 io.netty.util.concurrent。部分组件如下:
- Future及- Promise– 类似- ChannelFuture,但是不依赖- Channel
- EventExecutor及- EventExecutorGroup– 通用的事件循环API
他们是文档后面提到的channel API的基础。例如, ChannelFuture 继承了 io.netty.util.concurrent.Future,EventLoopGroup 继承了EventExecutorGroup。
Channel API 变更
经过4.0的这次大调整,许多 io.netty.channel 包下的类都已经不见了,所以3.x的应用没法通过简单的搜索-替换来升级到4.0。本小节会展示如此大的变化背后的思路历程,而不再一一描述所有的细节变化了。
修订过的ChannelHandler接口
Upstream → Inbound, Downstream → Outbound
初学者常对’upstream’ 及 ‘downstream’ 感到困惑,所以4.0中尽量使用了’inbound’ 及’outbound’。
新的 ChannelHandler 类层级关系
3.x中,ChannelHandler 仅是一个标记接口,ChannelUpstreamHandler, ChannelDownstreamHandler, 及LifeCycleAwareChannelHandler 定义了实际的handler方法。Netty4中,ChannelHandler合并了很多 LifeCycleAwareChannelHandler 中对于inbound及outbound handler都适用的方法。
public interface ChannelHandler {
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}下图展示了新的类层级关系:无event对象的 ChannelHandler
3.x中,每次I/O操作都会创建一个 ChannelEvent 对象。每次读/写也都会额外生成一个新的 ChannelBuffer。得益于将资源管理及buffer池交给JVM处理,这大大简化了Netty的内部实现。但是,这往往也是GC压力和偶尔观察到基于Netty的应用会处于高负载状态的源头。
4.0通过替换event对象为强类型方法调用的方式,几乎完全去掉了event对象的创建。3.x中类似 handleUpstream() 及 handleDownstream() 这种catch-all的event handler不再有了。每种event现在都会有单独的handler方法:
// 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 这样的event类型呢?4.0中 ChannelInboundHandler 有一个handler方法 userEventTriggered() 就是专门做这个的。
简化了的channel状态模型
3.x中,每有一个新的 Channel 连接创建,就至少会触发3个 ChannelStateEvent: 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 的动态注册、注销、再注册所引入的新状态,如下:write() 不会自动flush
4.0引入了 flush() 操作,可以显式对 Channel 的出站buffer进行flush,而write()操作本身不会自动进行flush。你可以认为这跟 java.io.BufferedOutputStream 类似,差别只是这里是运用于消息级别。
基于此变更,在进行写操作之后千万别忘了调用 ctx.flush()。当然,你也可以使用更便捷的 writeAndFlush()。
灵敏还不易出错的入站传输暂停机制
3.x使用了 Channel.setReadable(boolean) 这种很不直观的方式实现入站传输暂停机制。这导致ChannelHandlers之间的交互更为复杂,如果实现有误也很容易导致互相干扰。
4.0新增了 read() 这个出站操作。如果你通过 Channel.config().setAutoRead(false) 关闭了 auto-read 标记,那除非你显式的调用 read(),否则Netty不会自动进行任何读取。一旦你的 read() 操作完成,channel又会停止读取,并且会触发 channelReadSuspended() 这个入站事件,然后,你又可以重新执行 read() 操作了。你也可以拦截 read() 操作来做一些更高级的传输控制。
暂停接受入站连接
你无法让Netty 3.x停止接受入站连接,而只能阻塞I/O线程,或者关闭服务端socket。4.0中,当 auto-read 未设置时,就会切断(译者注:原文为respects,译者怀疑是笔误) read() 操作,就像一个普通的channel一样。
半关闭套接字(Half-closed sockets)
TCP及SCTP允许在不完全关闭socket的前提下关闭socket的出站传输。这样的socket称之为 ‘a half-closed socket’,用户可以通过调用 SocketChannel.shutdownOutput() 方法来产生半关闭socket。如果远端节点关闭了出站传输,SocketChannel.read(..) 就会返回 -1,看起来跟关闭的连接似乎没区别。
3.x没有 shutdownOutput() 操作。并且 当 SocketChannel.read(..) 返回 -1 时总是会关闭连接。
4.0中加入了 SocketChannel.shutdownOutput() 方法来支持半关闭socket,同时,用户可以设置 ChannelOption 为 ‘ALLOW_HALF_CLOSURE’ 来防止Netty在 SocketChannel.read(..) 返回 -1 时自动关闭连接。
灵活的 I/O 线程分配
3.x通过 ChannelFactory 创建 Channel,并且新创建的 Channel 会自动注册到一个隐藏的 I/O 线程上。4.0用新接口 EventLoopGroup 替代了 ChannelFactory,它由一个或者多个 EventLoop 组成。并且,新建的 Channel 不会自动注册到 EventLoopGroup,你必须显式调用 EventLoopGroup.register() 来完成注册。
基于此变更(即:ChannelFactory 与 I/O 线程的分离)就可以把不同的 Channel 实现注册到同样的 EventLoopGroup 上,或者同样的 Channel 实现注册到不同的 EventLoopGroup 上。例如,你可以运行NIO server socket, NIO client sockets, NIO UDP sockets及in-VM local channels在同样的 I/O 线程上。当编写需要极低延迟的代理服务器的时候,这将十分有用。
从已存在的 JDK socket 中创建Channel
3.x无法从已存在的 JDK socket 中创建 Channel,如 java.nio.channels.SocketChannel。4.0可以了。
从 I/O 线程中注销及重新注册Channel
3.x中,一旦 Channel 创建了,它就会绑定到一个 I/O 线程上,直到这个线程关闭为止。4.0中,用户可以把 Channel 从它的 I/O 线程中注销来获得它底层的 JDK sokcet 的完全控制权。比如,你可以利用高级non-blocking I/O Netty支持( high-level non-blocking I/O Netty provides)来处理复杂的协议,然后可以注销 Channel ,再切换为阻塞模式来传输文件,以达到最大的吞吐。当然,也可以把 Channel 再重新注册回去。
java.nio.channels.FileChannel myFile = ...; java.nio.channels.SocketChannel mySocket = java.nio.channels.SocketChannel.open();// 执行一些阻塞操作...// Netty 接管SocketChannel ch = new NioSocketChannel(mySocket); EventLoopGroup group = ...; group.register(ch); ...// 从 Netty 注销ch.deregister().sync();// 执行一些阻塞操作mySocket.configureBlocking(true); myFile.transferFrom(mySocket, ...);// 重新注册到另一个 event loop groupEventLoopGroup anotherGroup = ...; anotherGroup.register(ch);
使用 I/O 线程调度任意任务
把 Channel 注册到 EventLoopGroup 时,实际上是注册到了 EventLoopGroup 管理的一个 EventLoop 上。EventLoop 实现了 java.util.concurrent.ScheduledExecutorService。这意味着,用户可以在该channel所属的 I/O 线程上执行或者调度(execute or schedule)任意 Runnable 或者 Callable。基于后面会讲到的新的设计良好的线程模型,实现一个线程安全的handler将会十分容易。
public class MyHandler extends ChannelOutboundHandlerAdapter {
    ...    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise p) {
        ...
        ctx.write(msg, p);        // 调度一个写超时任务
        ctx.executor().schedule(new MyWriteTimeoutTask(p), 30, TimeUnit.SECONDS);
        ...
    }
}public class Main {
    public static void main(String[] args) throws Exception {        // 使用 I/O 线程运行任意任务
        Channel ch = ...;
        ch.executor().execute(new Runnable() { ... });
    }
}简化了的关闭
没有 releaseExternalResources() 了。你可以使用 EventLoopGroup.shutdownGracefully() 立即关闭所有已经打开的channel以及让所有的 I/O 线程自行停止。
类型安全的 ChannelOption
Netty有两种方式可以配置 Channel 的socket参数。一种是显式调用 ChannelConfig 的setters,如 SocketChannelConfig.setTcpNoDelay(true)。这是最类型安全的方式了。另一种是调用 ChannelConfig.setOption() 方法。有时候你认为有些socket选项是运行时配置的,这个方法刚好适用于这种场景。但3.x中,因为用户传入一个string和一个object,所以很容易出错。当用户传入错误的选项名或者值时,用户可能会收到一个 ClassCastException 错误,或者干脆只是被默默忽略掉。
4.0引入了新的类 ChannelOption 来提供类型安全的socket配置。
ChannelConfig cfg = ...;// Before:cfg.setOption("tcpNoDelay", true);
cfg.setOption("tcpNoDelay", 0);  // 运行时 ClassCastExceptioncfg.setOption("tcpNoDelays", true); // 打错了配置名 —— 静默忽略// After:cfg.setOption(ChannelOption.TCP_NODELAY, true);
cfg.setOption(ChannelOption.TCP_NODELAY, 0); // 编译错误AttributeMap
应用户要求,现在你可以在 Channel 及 ChannelHandlerContext 上附加任何对象了。Channel 及 ChannelHandlerContext 都实现了 AttributeMap 这个新接口。同时,ChannelLocal 及 Channel.attachment 被移除了。当 Channel 被GC时,其相应的属性值会被一起GC。
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 被完全重写了,当然,用途跟原来是一样的。它遵循了常见的样例代码中运行server或client的典型步骤。
新的bootstrap还支持流式API。
public static void main(String[] args) throws Exception {    // 配置 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, ...);
             }
         });        // 启动 server.
        ChannelFuture f = b.bind().sync();        // 等待socket关闭
        f.channel().closeFuture().sync();
    } finally {        // 关闭所有的event loop来终止所有线程
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();        // 等待所有线程终止
        bossGroup.terminationFuture().sync();
        workerGroup.terminationFuture().sync();
    }
}ChannelPipelineFactory → ChannelInitializer
你可能注意到上面例子中已经没有 ChannelPipelineFactory 了。它已经替换为支持更多 Channel 及 ChannelPipeline 配置的ChannelInitializer 了。
注意,不要自己创建 ChannelPipeline。Netty项目组根据至今报道的大量用例推断,用户创建自己的pipline实现或者继承其默认实现都不会带来什么好处。因此,ChannelPipeline 不再由用户创建了,而会被 Channel 自动创建。
ChannelFuture → ChannelFuture 及ChannelPromise
ChannelFuture 被拆分为 ChannelFuture 和 ChannelPromise。这不仅是生产者与消费者的异步操作的明确约定,同时可以更安全的使用链中(如filtering)返回的 ChannelFuture 了。因为 ChannelFuture 的状态是不可变的。
基于此变化,部分方法现在接受 ChannelPromise 而不是 ChannelFuture 来修改状态。
良好定义的线程模型
3.x中线程模型定义的并不好,尽管3.5尝试进行了改良也仍然不好。4.0定义了严格的线程模型,这样用户在编写ChannelHandler时不用再过多的担忧线程安全了。
- Netty不会并发的调用 ChannelHandler的方法,除非加了@Sharable注解。无论入站,出站或者生命周期事件handler方法都一样。- 用户不再需要同步入站或者出站事件handler方法了。
- 4.0仅允许标记 @Sharable注解的ChannelHandler被添加多次。
 
- 每个Netty的 ChannelHandler方法的调用都存在 happens-before 关系。- 用户不需要定义 volatile字段来保存handler的状态
 
- 用户不需要定义 
- 用户在添加handler到 ChannelPipeline时可以指定EventExecutor- 如果指定了, 则总会使用指定的 EventExecutor来调用ChannelHandler的方法
- 如果未指定,则总是使用其关联的 Channel中注册的EventLoop来调用handler的方法
 
- 如果指定了, 则总会使用指定的 
- 分配给handler或者channel的 EventExecutor及EventLoop线程总是单个线程- handler的方法总会在同一个线程中执行
- 如果指定了多线程的 EventExecutor或者EventLoop,首先会选中一个线程,并且直到注销为止都会使用这个线程
- 如果同一个pipeline中的两个handler分配了不同的 EventExecutor,他们会被同时调用。用户就需要关注pipeline中的共享数据的线程安全,即使共享数据只是被读取。
 
- 附加到 ChannelFuture上的ChannelFutureListeners总是运行在future关联的Channel被分配的EventLoop线程上
- The ChannelFutureListeners added to ChannelFuture are always invoked by the EventLoop thread assigned to the future’s associated Channel.
- 可以使用 ChannelHandlerInvoker控制Channel的事件顺序。DefaultChannelHandlerInvoker会立即执行EventLoop线程的事件和其他线程提交到EventExecutor的Runnable对象。下面的例子展示了在EventLoop线程中以及其他线程中与Channel交互时的潜在影响。
写排序 – 混合了 EventLoop 线程和其他线程
Channel ch = ...; ByteBuf a, b, c = ...;// 线程1 - 非EventLoop线程ch.write(a); ch.write(b);// .. 发生一些事情// EventLoop线程ch.write(c);// a,b,c写入底层传输通道的顺序是未定义的。// 如果出现了线程间交互而顺序又很重要,那么如何保证顺序性就是用户的职责了
没有 ExecutionHandler 了——移到了核心模块里
在添加 ChannelHandler 到 ChannelPipeline 的时候,可以指定 EventExecutor。这样pipeline 就总会使用指定的 EventExecutor 来调用handler方法。
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中handler创建和管理它自己的buffer(参考本文档中的Per-handler buffer章节),因此编解码框架内部进行了大量的变更。不过用户层面的变化倒不是很大。
- 核心编解码器类移到了 io.netty.handler.codec包中
- FrameDecoder重命名为- ByteToMessageDecoder
- OneToOneEncoder及- OneToOneDecoder替换为- MessageToMessageEncoder及- MessageToMessageDecoder
- decode(),- decodeLast(),- encode()的方法签名进行了些许调整,可支持泛型了,并且移除了多余的参数
Codec embedder → EmbeddedChannel
Codec embedder 替换为 io.netty.channel.embedded.EmbeddedChannel,用户可以测试包含编解码器在内的的任何类型的pipline了。
HTTP 编解码器
HTTP解码器会将单条HTTP消息解码为多个消息对象。
1 * HttpRequest / HttpResponse 0 - n * HttpContent 1 * LastHttpContent
参照最新的 HttpSnoopServer 样例获取更多细节。如果对于单条HTTP消息你不想处理多个消息对象,你可以传入 HttpObjectAggregator 到pipline中。HttpObjectAggregator 会将多个消息对象转变为单个 FullHttpRequest 或者 FullHttpResponse。
传输实现的变更
新增加的transport:
- OIO SCTP transport
- UDT transport
用例学习:移植Factorial样例
本节简单的展示了如何将Factorial样例从3.x移植到4.0。移植到4.0的Factorial样例已经放到了 io.netty.example.factorial 包里。请查看源码来了解所有细节修改。
移植服务端
- 使用新的bootstrap API来重写 FactorialServer.run()
- 没有 ChannelFactory了,请自行实例化NioEventLoopGroup(一个是接受入站连接,另一个则是处理已接受的连接)
- 重命名 FactorialServerPipelineFactory为FactorialServerInitializer
- 使其继承 ChannelInitializer<Channel>
- 通过 Channel.pipeline()获取ChannelPipeline而不是新建一个
- 使 FactorialServerHandler继承ChannelInboundHandlerAdapter
- 用 channelInactive()替换channelDisconnected()
- handleUpstream() 没用了
- messageReceived()重命名为- channelRead(),并且请根据方法签名调整参数
- ctx.write()替换为- ctx.writeAndFlush()
- 使 BigIntegerDecoder继承ByteToMessageDecoder<BigInteger>
- 使 NumberEncoder继承MessageToByteEncoder<Number>
- encode()不返回buffer了。使用- ByteToMessageDecoder填充encode过的数据到buffer里。
移植客户端
大部分跟移植服务端一样,不过当你要写入的流很大时则需要多加注意。
- 使用新的bootstrap API重写 FactorialClient.run()
- FactorialClientPipelineFactory重命名为- FactorialClientInitializer
- 使 FactorialClientHandler继承ChannelInboundHandler
-  文章2313
-  用户1336
-  访客11753564
十月以魔法和糖果收尾。
 
 























