这篇文章和你一块儿过下Netty的主发行版本的一些显著的改变和新特性,让你在把你的应用程序转换到新版本的时候有个概念。html
项目结构改变
Netty的包名从org.jboss.netty改成io.netty,由于咱们不在是JBoss.org的一部分了。java
二进制JAR包被分为了多个子模块以便用户可以从类路径中去掉非必需的特性。当前的结构以下:git
全部的Netty的Jar(除了netty-all外)包如今都是OSGI的bundle,可以用在你喜欢的OSGI容器上。github
经常使用API的变化
- 如今Netty里的大部分操做都支持简洁的方法链。
- 不能配置的getter如今都没有了get/is前缀 (如Channel.getRemoteAddress()→Channel.remoteAddress())
Buffer API变化
ChannelBuffer → ByteBuf
因为上文所提到的结构上的变化,buffer API如今能够做为一个单独的包被使用。为此,ChannelBuffer这个类型名也不那么讲得通了,而应该变动为ByteBuf。web
用来建立新buffer的功能类ChannelBuffers被拆分为两个功能类:Unpooled和BufUtil。就像这个名字所暗示的,4.0引入了一个新的池化的ByteBufs,它能够经过ByteBuf的分配器(Allocator)的对应实现ByteBufAllocator来得到。bootstrap
大多数的buffer变成了动态的,具有可配置的最大容量
在3.x时期,buffer分为固定和动态两种类型。一个固定buffer的容量在建立以后就没法改变,而动态buffer的容量在write*(译者按:writeByte,writeInt,writeLong...)方法须要更多空间时自动扩容。api
从4.0开始,全部buffer都变成了动态的。可是,相对于以前的动态进行了优化。你能够更容易也更安全的对一个buffer的容量进行扩大和缩小。之因此说它容易是由于有一个新的ByteBuf.capacity(int newCapacity)的方法。说它安全是由于你能够设置一个容量的最大值,以防止容量没有限制的增大。数组
2 |
ByteBuf buf = ByteBuf.buffer(); |
惟一的例外是那些使用wrappedBuffer方法建立的,包装(warp)了一个buffer或一个byte数组的buffer。你没法扩大它的容量,由于这样会使包装一个已有buffer的目的是去意义——减小内存的复制。若是你想要在包装了一个buffer以后改变它的容量,你应该从新建立一个拥有足够容量的buffer,而后将你想要包装的那个buffer的内容复制过来。promise
新接口: CompositeByteBuf
一个新的名叫CompositeByteBuf的接口为组合buffer(composite buffer)的实现定义了多种高级的操做。一个用户可使用组合buffer,以只比随机访问大一点的代价达到一个批量内存复制的目的。要建立一个新的组合buffer,能够像之前同样使用Unpooled.wrappedBuffer(... 译者注:此处省略号应该是指省略方法参数,下同)或Unpooled.compositeBuffer(...)。缓存
可预知的NIO buffer转型
在3.x中,ChannelBuffer.toByteBuffer()以及它的其余变体所提供的约定并不那么明确。用户没法肯定这些方法会返回一个拥有共享数据的视图buffer仍是一个拥有独立数据的经过复制获得的buffer。4.0将toByteBuffer()替换为ByteBuf.nioBufferCount(),nioBuffer(),以及nioBUffers()。若是调用nioBufferCount()返回0,用户老是能够经过调用copy().nioBuffer()来得到一个复制的buffer。
对小字节序变动的支持
对小字节序的支持经历了重大变化。在以前的版本中,一个用户为了获得一个小字节序的buffer有两种选择:特别指定一个LittleEndianHeapChannelBufferFactory;用目标字节序将已存在的buffer包装起来。4.0添加了一个新方法,ByteBuf.order(ByteOrder)。这个方法返回当前buffer对象的一个具备指定字节序的视图buffer:
01 |
import io.netty.buffer.ByteBuf; |
02 |
import io.netty.buffer.Unpooled; |
03 |
import java.nio.ByteOrder; |
05 |
ByteBuf buf = Unpooled.buffer( 4 ); |
08 |
System.out.format( "%08x%n" , buf.getInt( 0 )); |
10 |
ByteBuf leBuf = buf.order(ByteOrder.LITTLE_ENDIAN); |
12 |
System.out.format( "%08x%n" , leBuf.getInt( 0 )); |
15 |
assert buf == buf.order(ByteOrder.BIG_ENDIAN); |
Pooled ByteBuf
前面已经提到Netty引入了pooledByteBufinstances。这在不少方面都很实用,举列以下:
- 限制了GC压力,这是由于使用unpooled ByteBufs会形成沉重的分配与再分配问题
- Better handling of direct (native)ByteBuf更好的处理直接(本地)的ByteBuf
- 一个ByteBuf 能够被一个ByteBufAllocator包含.
01 |
public interface ByteBufAllocator { |
04 |
ByteBuf buffer( int initialCapacity); |
05 |
ByteBuf buffer( int initialCapacity, int maxCapacity); |
07 |
ByteBuf heapBuffer( int initialCapacity); |
08 |
ByteBuf heapBuffer( int initialCapacity, int maxCapacity); |
09 |
ByteBuf directBuffer(); |
10 |
ByteBuf directBuffer( int initialCapacity); |
11 |
ByteBuf directBuffer( int initialCapacity, int maxCapacity); |
14 |
CompositeByteBuf compositeBuffer(); |
15 |
CompositeByteBuf compositeBuffer( int maxNumComponents); |
16 |
CompositeByteBuf compositeHeapBuffer(); |
17 |
CompositeByteBuf compositeHeapBuffer( int maxNumComponents); |
18 |
CompositeByteBuf compositeDirectBuffer(); |
19 |
CompositeByteBuf compositeDirectBuffer( int maxNumComponents); |
要想从一个handler那里获取当前的 ByteBufAllocator,可使用ChannelHandlerContext.alloc()或Channel.alloc()方法:
2 |
ByteBuf buf = channel.alloc().buffer( 512 ); |
6 |
ChannelHandlerContext ctx = ... |
7 |
ByteBuf buf2 = ctx.alloc().buffer( 512 ); |
一旦一个ByteBuf被写入远程节点,它会再次自动的释放进入释放到池(the pool)里。
默认的ByteBufAllocator为PooledByteBufAllocator.若是你不但愿使用buffer pooling或使用你本身的allocator,你能够运用Channel.config().setAllocator(..),以及一个可供选择的 allocator,好比UnpooledByteBufAllocator。
Channel API的变化
在4.0中,许多io.netty.channel包中的类都经历大量修改,所以文本上的简单搜索-替换是没法让你基于3.x的程序迁移到4.0上。这个部分会尝试将这些重大变动背后的思考过程展现出来,而不仅是简单地做为展现全部变动。
翻新后的ChannelHandler接口
Upstream → Inbound, Downstream → Outbound
对于初学者来讲,术语'upstream'(译者注:直译为向上游,有点像TCP/IP协议栈中从下往上,从物理层最终到达应用层这么一个流程)和'downstream'有点让人迷惑。在4.0中,只要可能,都会使用'inbound'(译者注:直译为开往内地的,相对于upstream确实更贴切,即指数据从外部网络经历层层filter到达咱们的处理逻辑)和'outbound'来替换他们。
新的ChannelHandler继承层次
在3.x时代,ChannelHandler只是一个标记接口,而在ChannelUpstreamHandler、ChannelDownstreamHandler、LifeCycleAwareChannelHandler定义了具体的处理器方法。在Netty 4中,ChannelHandler将LifeCycleAwareChannelHandler接口和一堆实现辅助方法融合到了一块儿,具体见代码:
01 |
public interface ChannelHandler { |
03 |
void beforeAdd(ChannelHandlerContext ctx) throws Exception; |
04 |
void afterAdd(ChannelHandlerContext ctx) throws Exception; |
05 |
void beforeRemove(ChannelHandlerContext ctx) throws Exception; |
06 |
void afterRemove(ChannelHandlerContext ctx) throws Exception; |
08 |
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; |
09 |
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; |
下方的图表描述了这个新的类型集成层次:
fixme(原文中尚未插入此图)
事件对象从ChannelHandler中消失了
在3.x时代,全部的I/O操做都会建立一个新的ChannelEvent对象。对每一个读或写的操做,还会额外建立一个新的ChannelBuffer对象。因为将资源管理和buffer的池化交给了JVM,这实际上极大地简化了Netty的内部实现。可是,基于Netty开发的应用在高负载下运行时,有时会观察到GC(Garbage Collection)的压力增大或变化不定,这些问题的根源也来自于这里。
4.0经过把事件对象替换为直接与类型相对应(译者注:原文为strongly typed,可是我以为直译为强类型不太容易理解)的方法调用,几乎彻底避免了事件对象的建立。3.x中,有相似于handleUpstream()和handleDownstream()这种可以捕获全部相关类型事件的处理器方法,4.0中你将不会再看到它们的身影了。全部的事件类型如今都有各自对应的处理器方法:
02 |
void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception; |
03 |
void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception; |
06 |
void channelRegistered(ChannelHandlerContext ctx) throws Exception; |
07 |
void channelUnregistered(ChannelHandlerContext ctx) throws Exception; |
08 |
void channelActive(ChannelHandlerContext ctx) throws Exception; |
09 |
void channelInactive(ChannelHandlerContext ctx) throws Exception; |
10 |
void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception; |
12 |
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelFuture future) throws Exception; |
14 |
ChannelHandlerContext ctx, SocketAddress remoteAddress, |
15 |
SocketAddress localAddress, ChannelFuture future) throws Exception; |
16 |
void disconnect(ChannelHandlerContext ctx, ChannelFuture future) throws Exception; |
17 |
void close(ChannelHandlerContext ctx, ChannelFuture future) throws Exception; |
18 |
void deregister(ChannelHandlerContext ctx, ChannelFuture future) throws Exception; |
19 |
void flush(ChannelHandlerContext ctx, ChannelFuture future) throws Exception; |
20 |
void read(ChannelHandlerContext ctx); |
21 |
void sendFile(ChannelHandlerContext ctx, FileRegion region, ChannelPromise promise) throws Exception; |
ChannelHandlerContext类也被修改来反映上述提到的变化:
5 |
ctx.fireInboundBufferUpdated(); |
全部这些变化意味着用户没法去扩展ChannelEvent这个已经不存在的接口了。那用户要怎样才能定义他或她本身的事件类型呢,就像IdleStateEvent?4.0中的ChannelHandler有一个处理器方法叫作userEventTriggered(),它就是被设计用来知足这种特殊的用户需求。
Simplified channel state model
在3.x中,当一个新的Channel被建立并链接成功,至少三个ChannelStateEvent会被触发:channelOpen、channelBound以及channelConnected。当一个Channel关闭,则对应channelDisconnected、channelUnbound以及channelClosed三个事件。
fixme
可是,触发这么多事件的意义并不那么明显。若是在一个Channel进入可读或可写的状态时通知用户,想来会更有帮助。
fixme
channelOpen、channelBound和channelConnected被合并为channelActive。channelDisconnected、channelUnbound和channelClosed被合并为channelInactive。相似的,Channel.isBound()和Channel.isConnected()也被合并为了Channel.isActive()。
须要注意的是,channelRegistered和channelUnregistered这两个事件与channelOpen和channelClosed具备的意义是不同的。它们(channelRegistered和channelUnregistered)是在支持Channel的动态注册、注销以及再注册时被引入的,就像下图所示:
fixme
每一个处理器的缓存
不像3.x那样在每次读操做都简历一个新堆里的缓存来触发上游的MessageEvent,4.0不会每次都建立新的 缓存。它直接从socket中读取数据到由用户的ChannelInboundByteHandler和ChannelInboundMessageHandler实现建立的入站缓存。
由于由上述处理器建立的入站缓存直到关联的通道关闭前都会重用,因此在上面的GC和内存带宽消耗都能保持较小。一样,当接收到的数据被销毁时用户已经完成操做,codec的实现就变得更简单和有效了。
在建立出站缓存时也是差很少的(不会新建)。用户的ChannelOutBoundBYteHandler和ChannelOutboundMessageHandler来操做。
不须要每条消息都有一个事件
4.0里再也不有了messageReceived或writeRequested处理器方法。它们被inboundBufferUpdated和flush代替了。用户的入队一个或多个消息到一个入站(或出站)缓存同时会出发一个inboundBUfferUpdated(或flush)事件。
01 |
public void inboundBufferUpdated(ChannelHandlerContext ctx) { |
02 |
Queue<MyMessage> in = ctx.inboundMessageBuffer(); |
03 |
Queue<MyNewMessage> out = ctx.nextInboundMessageBuffer(); |
05 |
MyMessage m = in.poll(); |
09 |
MyNewMessage decoded = decode(m); |
12 |
ctx.fireInboundBufferUpdated(); |
15 |
public void flush(ChannelHandlerContext ctx, ChannelFuture future) { |
16 |
Queue<MyNewMessage> in = ctx.outboundMessageBuffer(); |
17 |
Queue<MyMessage> out = ctx.nextOutboundMessageBuffer(); |
19 |
MyNewMessage m = in.poll(); |
23 |
MyMessage encoded = encode(m); |
做为选择,用户可以在每一个单独的入站(或出站)消息中触发这样的事件来模拟老的行为,尽管相对新方法来讲效率更低。
消息处理器 vs. 字节处理器
在3.x里一个MessageEvent持有一个任意的对象。它可以是一个ChannelBuffer或是一个用户自定义的对象,它们都是一样对待的:
02 |
public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) { |
03 |
Object msg = evt.getMessage(); |
04 |
if (msg instanceof ChannelBuffer) { |
05 |
ChannelBuffer buf = (ChannelBuffer) msg; |
08 |
MyMessage myMsg = (MyMessage) msg; |
在4.0里,它们就分别对待了,由于一个处理器再也不处理一个独立的消息,而是处理多种多样的消息:
01 |
public void inboundBufferUpdated(ChannelHandlerContext ctx) { |
02 |
if (ctx.hasInboundByteBuffer()) { |
03 |
ByteBuf buf = ctx.inboundByteBuffer(); |
06 |
Queue<MyMessage> buf = ctx.inboundMessageBuffer(); |
08 |
MyMessage msg = buf.poll(); |
你可能发现一个ServerChannel的处理器是一个入站缓存是Queue<Channel>的入站处理器是较为有趣的。
处理器适配器
大多数用户都发现建立和管理它的生命周期是繁琐的,所以它支持用户扩展预约义好的适配器类来使得更方便:
- ChannelHandlerAdapter
- ChannelStateHandlerAdapter
- ChannelOperationHandlerAdapter
- ChannelInboundMessageHandlerAdapter
- ChannelInboundByteHandlerAdapter
- ChannelOutboundMessageHandlerAdapter
- ChannelOutboundByteHandlerAdapter
明智的和不易出错的入站流量挂起
3.x有一个由Channel.setReadable(boolean)提供的不是很明显的入站流量挂起机制。它引入了在ChannelHandler之间的复杂交互操做,同时处理器因为不正确实现而很容易互相干扰。
4.0里,新的名为read()的出站操做增长了。若是你使用Channel.config().setAutoRead(false)来关闭默认的auto-read标志,Netty不会读入任何东西,直到你显式地调用read()操做。一旦你发布的read()操做完成,同时通道再次中止读,一个名为channelReadSuspended()的入站事件会触发一遍你可以从新发布另外的read()操做。你一样也能够拦截read()操做来执行更多高级的流量控制。
暂停接收传入的链接
在3.x里,没有方法让一个用户告诉Netty来厅子接收传入链接,除非是阻塞I/O线程或者关闭服务器socket。在aotu-read标志没有设置的时候,4.0涉及到的read()操做就像一个普通的通道。
半关闭socket
TCP和SCTP容许用户关闭一个socket的出站流量而不用彻底关闭它。这样的socket被称为“半关闭socket”,同时用户可以经过调用SocketChannel.shutdownOutput()方法来获取一个半关闭socket。若是一个远端关闭了出站通道,SocketChannel.read(..)会返回-1,这看上去并无和一个关闭了的连接有什么区别。
3.x没有shutdownOutput()操做。一样,它老是在SocketChannel.read(..)返回-1的时候关闭连接。
要支持半关闭socket,4.0增长了SocketChannel.shutdownOutput()方法,同时用户能设置“ALLOW_HALF_CLOSURE”的ChanneOption来阻止Netty在SocketChannel.read(..)返回-1的时候自动关闭连接。
灵活的I/O线程分配
在3.x里,一个Channel是由ChannelFactory建立的,同时新建立的Channel会自动注册到一个隐藏的I/O线程。4.0使用新的名为EventLoopGroup的接口来替换ChannelFactory,它由一个或多个EventLoop来构成。一样,一个新的Channel不会自动注册到EventLoopGroup,但用户能够显式调用EventLoopGroup.register()来注册。
感谢这个变化(举例来讲,分离了ChannelFactory和I/O线程),用户能够注册不一样的Channel实现到同一个EventLoopGroup,或者同一个Channel实现到不一样的EventLoopGroup。例如,你能够运行一个NIO服务器socket,NIO UDP socket,以及虚拟机内部的通道在同一个I/O线程里。在编写一个须要最小延迟的代理服务器时这确实颇有用。
可以从一个已存在的jdk套接字上建立一个Channel
3.x没提供方法从已存在的jdk套接字(如java.nio.channels.SocketChannel)建立一个新的通道。如今你能够用4.0这样作了。
取消注册和从新注册一个Channel从/到一个I/O线程
一旦一个新的Channel在3.x里建立,它彻底绑定到一个单一的I/O线程上,直到它底层的socket关闭。在4.0里,用户可以从I/O线程里取消注册一个Channel来彻底控制它底层jdk套接字。例如,你可以利用Netty提供的高层次无阻塞I/O的优点来解决复杂的协议,而后取消注册Channel而且切换到阻塞模式来在可能的最大吞吐量下传输一个文件。固然,它可以再次注册已经取消了注册的Channel。
01 |
java.nio.channels.FileChannel myFile = ...; |
02 |
java.nio.channels.SocketChannel mySocket = java.nio.channels.SocketChannel.open(); |
08 |
SocketChannel ch = new NioSocketChannel(mySocket); |
09 |
EventLoopGroup group = ...; |
14 |
ch.deregister().sync(); |
17 |
mySocket.configureBlocking( false ); |
18 |
myFile.transferFrom(mySocket, ...); |
21 |
EventLoopGroup anotherGroup = ...; |
22 |
anotherGroup.register(ch); |
调度任意的任务到一个I/O线程里运行
当一个Channel被注册到EventLoopGroup时,Channel其实是注册到由EventLoopGroup管理EventLoop中的一个。EventLoop实现了java.utilconcurrent.ScheduledExecutorService接口。这意味着用户能够在一个用户通道归属的I/O线程里执行或调度一个任意的Runnable或Callable。随着新的娘好定义的线程模型的到来(稍后会介绍),它变得极其容易地编写一个线程安全的处理器。
01 |
public class MyHandler extends ChannelOutboundMessageHandlerAdapter { |
03 |
public void flush(ChannelHandlerContext ctx, ChannelFuture f) { |
08 |
ctx.executor().schedule( new MyWriteTimeoutTask(), 30 , TimeUnit.SECONDS); |
14 |
public static void main(String[] args) throws Exception { |
17 |
ch.executor().execute( new Runnable() { ... }); |
简化的关闭
releaseExternalResources()没必要再用了。你能够经过调用EventLoopGroup.shutdown()直接地关闭全部打开的链接同时使全部I/O线程中止,就像你使用java.util.concurrent.ExecutorService.shutdown()关闭你的线程池同样。
类型安全的ChannelOptions
有两个方法来配置Netty的Channel的socket参数。第一个是明确地调用ChannelConfig的setter,例如SocketChannelConfig.setTcpNoDelay(true)。这是最为类型安全的方法。另一个是调用ChannelConfig.setOption()方法。有时候你不得不决定在运行时的时候socket要配置什么选项,同时这个方法在这种状况下有点不切实际。然而,在3.x里它是容易出错的,由于一个用户必需用一对字符串和对象来指定选项。若是用户调用了错误的选项名或者值,他或她将会赵宇到一个ClassCastException或指定的选项甚至可能会默默地忽略了。
4.0引入了名为ChannelOption的新的类型,它提供了类型安全地访问socket选项。
01 |
ChannelConfig cfg = ...; |
04 |
cfg.setOption( "tcpNoDelay" , true ); |
05 |
cfg.setOption( "tcpNoDelay" , 0 ); |
06 |
cfg.setOption( "tcpNoDelays" , true ); |
09 |
cfg.setOption(ChannelOption.TCP_NODELAY, true ); |
10 |
cfg.setOption(ChannelOption.TCP_NODELAY, 0 ); |
AttributeMap
在回应用户指令里,你能够附加任意的对象到Channel和ChannelHandlerContext。一个名为AttributeMap的新接口被加入了,它被Channel和ChannelHandlerContext继承。做为替代,ChannelLocal和Channel.attachment被移除。这些属性会在他们关联的Channel被垃圾回收的同时回收。
01 |
public class MyHandler extends ChannelInboundMessageHandlerAdapter<MyMessage> { |
03 |
private static final AttributeKey<MyState> STATE = |
04 |
new AttributeKey<MyState>( "MyHandler.state" ); |
07 |
public void channelRegistered(ChannelHandlerContext ctx) { |
08 |
ctx.attr(STATE).set( new MyState()); |
09 |
ctx.fireChannelRegistered(); |
13 |
public void messageReceived(ChannelHandlerContext ctx, MyMessage msg) { |
14 |
MyState state = ctx.attr(STATE).get(); |
新的bootstrap API
bootstrap API已经重头重写,尽管它的目的仍是同样;它执行须要使服务器或客户端运行的典型步骤,一般能在样板代码里找到。新的bootstrap一样采起了流畅的接口。
01 |
public static void main(String[] args) throws Exception { |
03 |
ServerBootstrap b = new ServerBootstrap(); |
05 |
b.group( new NioEventLoopGroup(), new NioEventLoopGroup()) |
06 |
.channel( new NioServerSocketChannel()) |
07 |
.option(ChannelOption.SO_BACKLOG, 100 ) |
09 |
.childOption(ChannelOption.TCP_NODELAY, true ) |
10 |
.childHandler( new ChannelInitializer<SocketChannel>() { |
12 |
public void initChannel(SocketChannel ch) throws Exception { |
13 |
ch.pipeline().addLast(handler1, handler2, ...); |
18 |
ChannelFuture f = b.bind().sync(); |
21 |
f.channel().closeFuture().sync(); |
ChannelPipelineFactory → ChannelInitializer
和你在上面的例子注意到的同样,ChannelPipelineFactory再也不存在了。而是由ChannelInitializer来替换,它给予了在Channel和ChannelPipeline的配置的更多控制。
请注意,你不能本身建立一个新的ChannelPipeline。经过观察目前为止的用例报告,Netty项目队伍总结到让用户去建立本身的管道实现或者是继承默认的实现是没有好处的。所以,ChannelPipeline再也不让用户建立。ChannelPipeline由Channel自动建立。
ChannelFuture拆分为ChannelFuture和ChannelPromise
ChannelFuture已经被拆分为ChannelFuture和ChannelPromise了。这不只仅是让异步操做里的生产者和消费者间的约定更明显,一样也是得在使用从链中返回的ChannelFuture更加安全,由于ChannelFuture的状态是不能改变的。
因为这个编号,一些方法如今都采用ChannelPromiser而不是ChannelFuture来改变它的状态。
良好定义的线程模型
在3.x里并无良好设计的线程模型,尽管曾经要修复线程模型在3.5的不一致性。4.0定义的一个严格的线程模型来帮助用户编写ChannelHandler而没必要担忧太多关于线程安全的东西。
- Netty将不会再同步地调用ChannelHandler的方法了,除非ChannelHandler由@Shareable注解。这不会理会处理器方法的相似——入站、操做、或者是生命周期时间处理器方法。
- 用户再也不须要同步入站或出站的事件处理器方法。
- 4.0不容许加入加入一个ChannelHandler超过一次,除非它由@Sharable注解。
- 每一个由Netty调用的ChannelHandler的方法之间的关系老是happens-before。
- 用户不用定义一个volatile字段来保存处理器的状态。
- 用户可以在他加入一个处理器到ChannelPipeline的时候指定EventExecutor。
- 若是有指定,ChannelHandler的处理器方法老是由自动的EventExecutor来调用
- 若是没指定,处理器方法老是由它关联的Channel注册到的EventLoop来调用。
- 声明到一个处理器的EventExecutor和EventLoop老是单线程的。
- 处理器方法老是由相同线程调用。
- 若是指定了多线程的EventExecutor或EventLoop,线程中的一个会被选择,而后选择到的线程将会被使用,直到取消注册。
- 若是在相同管道里的两个处理器声明到不一样的EventExecutor,它们会同时被调用。若是多个一个处理器去访问共享数据,用户须要注意线程安全,即便共享数据只能被相同管道里的处理器访问。
- 加入到ChannelFuture的ChannelFutureListener老是由关联到future相关的Channel的EventLoop线程调用。
再也不有ExecutionHandler ——它包含到核内心
在你加入一个ChannelHandler到一个ChannelPipeline来告诉管道老是经过指定的EventExecutor调用加入的ChannelHander处理器的方法的时候,你能够指定一个EventExecutor。
2 |
ChannelPipeline p = ch.pipeline(); |
3 |
EventExecutor e1 = new DefaultEventExecutor( 16 ); |
4 |
EventExecutor e2 = new DefaultEventExecutor( 8 ); |
6 |
p.addLast( new MyProtocolCodec()); |
7 |
p.addLast(e1, new MyDatabaseAccessingHandler()); |
8 |
p.addLast(e2, new MyHardDiskAccessingHandler()); |
EventExecutor是EventLoop的超类,同时也继承了ScheduledExecutorService。
fixme
编码解码器框架变化
在编码解码器框架里有实质性的内部改变,由于4.0须要一个处理器来建立和管理它的缓存(看这篇文章的每一个处理器缓存部分。)然而,从用户角度来看这些变化都不是很大的。
- 核心编码界面器类移到io.netty.handler.codec包里。
- FrameDecoder重命名为ByteToMessageDecoder。
- OneToOneEncoder和OneToOneDecoder由MessageToMessageEncoder和MessageToMessageDecoder替换。
- decode(),decodeLast(),encode()的方法前面稍微改变了来支持泛型同时移除冗余参数。
编码解码器嵌入器→ EmbeddedChannel
编码解码器嵌入器已经被 io.netty.channel.embedded.EmbeddedByteChannel和EmbeddedMessageChannel替换了。EmbeddedChannel容许用户对任何包含编码解码器的管道进行单元测试。
HTTP编码解码器
HTTP解码器如今在每一个HTTP消息中总生成多个消息对象:
1 |
1 * HttpRequest / HttpResponse |
要看更多的细节,请到转到已更新了的HttpSnoopServer例子。若是你但愿为一个单一的HTTP消息处理多个消息,你能够把HttpObjectAggregator放入管道里。HttpObjectAggregator会把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse。
传输实现的变化
下面是传输协议新加入的东西:
- 使用NIO.2AsynchronousSocketChannel的AIO套接字传输实现。
- OIO SCTP 传输实现
- UDT 传输实现
用例学习:移植示例Factorial
这部分粗略地展现把示例Factorial从3.0移植到4.0的步骤。示例Factorial已经移植到4.0了,它放在io.netty.example.factorial包里。请浏览示例的源代码来看下每一处的变化。
移植服务端
- 重写FactorialServer.run()方法来使用新的 bootstrap API。
- 再也不有ChannelFactory了。 由你本身去实例化NioEventLoop(一个是用来接收接入的连接,另外的就用来处理接收到的连接)。
- 从命名FactorialServerPipelineFactory为FactorialServerInitializer。
- 让它继承ChannelInitializer<Channel>。
- 取代建立新的ChannelPipeline,经过Channel.pipeline()来获取。
- 让FactorialServerHandler继承sChannelInboundMessageHandlerAdapter<BigInteger>。
- 用channelInactive()来替换channelDisconnected()。
- handleUpstream()不能再使用了。
- 让BigIntegerDecoder继承ByteToMessageDecoder<BigInteger>。
- 让NumberEncoder继承MessageToByteEncoder<Number>。
- encode()不在返回一个缓存了。由ByteToMessageDecoder来提供填充编码好的数据到缓存里。
移植客户端
大部分和移植服务端差很少,但你要在你编写一个潜在的大数据流时要多注意下。
- 重写FactorialClient.run()方法来使用新的bootstrap API。
- 重命名FactorialClientPipelineFactory为FactorialClientInitializer。
- 使FactorialClientHandler继承ChannelInboundMessageHandlerAdapter<BigInteger>
- 在这一点,你发如今4.0里没有了Channel.isWritable()或者channelInterestChanged()。做为代替,你本身来管理那些未决定的写操做。新的sendNumbers()看起来以下:
01 |
private void sendNumbers() { |
03 |
boolean finished = false ; |
04 |
MessageBuf<Object> out = ctx.nextOutboundMessageBuffer(); |
05 |
while (out.size() < 4096 ) { |
07 |
out.add(Integer.valueOf(i)); |
15 |
ChannelFuture f = ctx.flush(); |
17 |
f.addListener(numberSender); |
21 |
private final ChannelFutureListener numberSender = new ChannelFutureListener() { |
23 |
public void operationComplete(ChannelFuture future) throws Exception { |
24 |
if (future.isSuccess()) { |