ByteBuf、ChannelHandler和ChannelPipeline、EventLoop和线程模型详述
ByteBuf
工作方式:ByteBuf维护了两个不同的的索引,一个用于读取,一个用于写入。从ByteBuf读取数据时readerIndex将会递增已被读取字节数,写入ByteBuf时,writeIndex也会递增。
注:名称以read\write开头方法会推进索引,而set\get开头的方法不会。
ByteBuf的使用模式
- 堆缓冲区,将数据存储在JVM的堆空间中。这种模式被称为支撑数组(backing array),能在没有使用池化的情况下提供快速的分配和释放内存
- 直接缓冲区:允许JVM通过本地调用来分配内存。直接缓冲区的内容不在会被常规垃圾回收的堆区,缺点在于其内存分配和释放都较为昂贵。
- 复合缓冲区:可以为多个ByteBuf提供一个聚合视图,通过CompositeByteBuf子类将多个缓冲区表示为单个合并缓冲区的虚拟表示
ChannelHandler
Channel的生命周期状态
状态 | 描述 |
---|---|
ChannelRegistered | Channel已经被注册到EventLoop |
ChannelUnregistered | Channel已经被创建,但还未注册到EventLoop |
ChannelActive | Channel处于活动状态(已经链接到远程节点),可以接受和发送数据 |
ChannelInactive | Channel没有链接到远程节点 |
当Channel的生命周期状态改变时,会生成对应的事件,转发给ChannelPipeline的ChannelHandler,对其做出响应
ChannelHandler的生命周期方法(每个方法都会接受一个ChannelHandlerContext参数)
类型 | 操作 |
---|---|
handlerAdded | 当把ChannelHandler添加到ChannelPipeline中时调用 |
handlerRemoved | 从ChannelPipeline中移除ChannelHandler时调用 |
execptionCaught | 处理过程中ChannelPipeline出现错误时调用 |
ChannelHandler的子接口:
- ChannelInboundHandler——处理入站数据以及各种状态变化
- channelRegister
- channelUnregistered
- channelActive
- channelInactive
- channelRead
- ChannelWritabilityChanged(Channel可写状态改变时调用)
- userEventTriggered
- ChannelOutboundHandler——处理出站数据并且允许拦截所有操作
- bind(绑定到本地地址)
- connect(链接到远程地址)
- disconnect
- close
- deregister
- read
- flush
- write
ChannelPipeline接口
------一个拦截流经Channel的入站和出站事件的ChannelHandler实例链。
每个新创建的Channel都会分配给一个新的Channelpipeline,Channel不能附加到另外一个ChannelPipeline,也不能分离当前的。
- ChannelPipeline保存了与Channel相关联的ChannelHandler
- ChannelPipeline可以根据需要,通过添加或删除ChannelHandler来动态的修改
- ChannelPipeline有着丰富的API用以调用,以响应出站和入站事件
- ChannelHandlerContext
ChannelHandlerContext使得ChannelHandler能够和它的ChannelPipeline以及其他的Channelhandler交互
在ChannelPipeline中传播事件时,它会测试ChannelPipeline中的下一个ChannelHandler的类型是否与事件的运动方向相匹配,如不匹配则跳过该ChannelHandler前进到下一个,直到找到匹配的为止。
ChannelHandler用于修改ChannelPipeline的方法
名称 | 描述 |
---|---|
addFirst addBefore addAfter addLast | 将一个ChannelHandler添加到ChannelPipeline中 |
remove | 将一个ChannelHandler从ChannelPipeline中移除 |
replace | 将ChannelPipeline中的一个ChannelHandler替换为另一个ChannelHandler |
ChannelHandlerContext接口
ChannelHandlerContext代表了ChannelHandler和Channel Pipeline之间的关联,当有ChannelHandler添加到ChannelPipeline中时就会创建ChannelHandlerContext
一个ChannelHandler可以从属于多个ChannelPipeline,所以可以绑定多个ChannelHanlderContext实例。多个ChannelPipeline共享一个ChannelHandler时,对应的ChannelHandler必须使用@Sharable注解标注,而且应该确定了ChannelHandler时线程安全的情况下才能使用。
异常处理
处理入站异常
- 发生异常时会继续按照入站方向流动,确保异常总是会被处理。exceptionCaught的默认实现是简单的将异常转发给下一个ChannelHandler
- 如果异常到达ChannelPipeline的尾端,它会被记录成未被处理
- 若要自定义处理逻辑,需重写exceptionCaught方法
处理出站异常
- 每个出站操作将会返回一个ChannelFuture,注册到ChannelFuture的ChannelFutureListener将会在操作完成时通知该操作是成功了还是出错了
- 几乎所有ChannelOutboundHandler都有ChannelPromise实例,可以分配用于异步通知的监听器,也可以提供立即通知的可写方法setSuccess和setFaulure
EventLoop和线程模型
线程模型指定了操作系统、编程语言、框架或者应用程序的上下文的线程管理的关键方面。
事件循环:运行任务来处理在链接的生命周期内发生的事件
一个EventLoop将由一个永远不会改变的Thread驱动,同时任务可以直接提交给EventLoop实现,以立即执行或调度执行。
在Netty4中,所有I/O操作和事件都由已经被分配给EventLoop的那个Thread处理。
如果当前调用线程正式支撑EventLoop的线程,那么所提交的代码就会被直接执行,否则EventLoop将会调度该任务以便稍后执行,并将他放入队列中,当EventLoop下次处理它的事件时,就会执行队列中的事件。
使用EventLoop调度任务
//调度任务在从现在的60秒后执行
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable(){
@Override
public void main(){
System.out.println("60 seconds later...");
}
},60,TimeUnit.SECONDS);