netty权威指南学习笔记
netty实战学习笔记
Netty的ChannelPipeline和ChannelHandler机制类似于Servlet和Filter过滤链.Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有 I/O事件 拦截器ChannelHandler的链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的通过新增和删除ChannelHandler来实现对不同业务逻辑定制。
ChannelHandler
ChannelHandler类似于J2EE中的Filter,负责对I/O事件进行拦截,它可以选择性的拦截和处理自己感兴趣的事件,也可以透传和终止事件的传输。
基于ChannelHandler接口,用户可以方便地进行业务逻辑定制,例如日志打印,统一封装异常信息,性能统计和消息编辑码等。
ChannelHandler支持注解,目前注解有两种:
- Sharable: 多个Pipeline公用一个ChannelHandler。
- Skip: 被Skip注解的方法不会被调用,直接被忽略。
@ChannelHandler.Sharable
public class MyChannelHandler extends ChannelHandlerAdapter {
@Skip
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
}
}
ChannelHandler 的生命周期
Channel的类结构
Netty4.x 升级到到5.x,5.x简化处理器层次:
- ChannelInboundHandler和ChannelOutboundHandler被删除,整合为ChannelHandler。ChannelHandler现在包含输入和输出的处理方法。
- ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter被废弃(@Deprecated),由ChannelHandlerAdapter代替。
为了便于理解,先学习4.x的类结构,Netty 定义了下面两个重要的 ChannelHandler 子接口:
- ChannelInboundHandler——处理入站数据以及各种状态变化;
- ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。
ChannelInboundHandler 接口
入站操作和数据将由 ChannelInboundHandler 处理。
ChannelOutboundHandler 接口
出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。
ChannelPromise与ChannelFuture ,ChannelOutboundHandler中的大部分方法都需要一个
ChannelPromise参数, 以便在操作完成时得到通知。 ChannelPromise是ChannelFuture的一个
子类,其定义了一些可写的方法,如setSuccess()和setFailure(), 从而使ChannelFuture不可变
ChannelHandlerAdapter功能说明
对于大多数的ChannelHandler会选择性地拦截或者处理某些事件,其他的事件会忽略,由下一个ChannelHandler进行拦截处理。这就导致了一个问题,用户ChannelHandler必须要实现ChanelHandler所有的接口方法,包括它不关心的事件处理接口,这会导致用户的业务代码臃肿,代码可维护性也会变得很差。
为了解决这个问题,Netty提供了ChannelHandlerAdapter基类,它的所有接口实现都是事件透传(且添加了@Skip注解),如果用户对哪个事件感兴趣,只需要覆盖对应的方法即可:
public class ChannelHandlerAdapter implements ChannelHandler {
// ....
@Skip
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
@Skip
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
}
ChannelHandlerAdapter类图
ByteToMessageDecoder :将读取到的字节数组或者字节缓冲区解码为业务可以使用的POJO对象。用户只需要实现decode(ChannelHandlerContext ctx, ByteBuf in, List out)方法即可,注意:ByteToMessageDecoder并没有考虑TCP拆包/粘包场景
MessageToMessageDecoder:它实际上是Netty的一个二次解码器,它的职责将一个对象二次解码为其他对象。为什么称之为二次解码器?因为从SocketChannel读取的TCP数据报是ByteBuffer,它实际上是字节数组,首先要将它解码为一个Java对象,然后再通过MessageToMessageDecoder将它解码为另一个POJO对象。例如Http+xml协议,通常第一次解码为HttpRequest对象,然后将HttpRequest消息体字符串进行二次解码,将XML格式字符串解码为POJO对象。
- MessageToByteEncoder:它负责将POJO对象转换为ByteBuf,用户集成MessageToByteEncoder需要实现它的encode(ChannelHandlerContext ctx, I msg, ByteBuf out)方法。
- MessageToMessageEncoder:实现encode(ChannelHandlerContext ctx, I msg, List out)方法即可。它与MessageToByteEncoder的不同之处是:它输出的是List
- ByteToMessageCodec : ByteToMessageDecoder + MessageToByteEncoder
- MessageToMessageCodec: MessageToMessageDecoder + MessageToMessageEncoder
ChannelPipeline
ChannelPipeline 功能说明
每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。ChannelPipeline是ChannelHandler的容器,它负责Channel的管理和事件拦截调度,ChannelPipeline可以根据需要,**动态的增加或者删除**ChannelHandler。
如果一个入站事件被触发,它将从ChannelPipeline头部一直传播到尾端,如果出站事件触发,则按照相反的方向传播。
ChannelPipeline的事件分为inbound事件和outbound事件。
ChannelPipeline的入站事件
入站事件通常是由I/O线程触发,如TCP建立连接,链路关闭事件,读事件,异常通知事件
ChannelPipeline的出站事件
出站事件通常是用户主动发起网路I/O操作,如用户发起连接操作,绑定操作,消息发送操作等。
ChannelPipeline类结构
ChannelHandlerContext
ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,
每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。
ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler之间的交互。
- ChannelHandlerContext和ChannelHandler之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
ChannelHandlerContext接口-主要方法
ChannelHandlerContext有很多的方法,其中一些方法也存在于Channel和ChannelPipeline本身上,但是有一点重要的不同。如果调用Channel或者ChannelPipeline上的这些方法,它们将沿着整个ChannelPipeline进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个能够处理该事件的ChannelHandler。
通过Channel或者ChannelPipeline写入
void channelWrite(){
ChannelHandlerContext ctx = null;
Channel channel = ctx.channel();
//通过 Channel 写入缓冲区, 将会导致写入事件从尾端到头部地流经 ChannelPipeline。
channel.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
}
void pipelineWrite(){
ChannelHandlerContext ctx = null;
ChannelPipeline pipeline = ctx.pipeline();
//通过 ChannelPipeline 写入缓冲区, 同Channel
pipeline.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));
}
调用Channel或ChannelPipeline上的write()方法将一直传播事件通过整个ChannelPipeline,但是在ChannelHandler的级别上,事件从一个ChannelHandler到下一个ChannelHandler的移动是由ChannelHandlerContext上的调用完成的.
为什么需要从ChannelPipeline的某个特定的点开始事件传播呢?
- 为了减少将事件传经对它不感兴趣的 ChannelHandler 所带来的开销。
- 为了避免将事件传经那些可能会对它感兴趣的 ChannelHandler。
通过ChannelHandlerContext写入
void contextWrite(){
ChannelHandlerContext ctx = null;
//通过ChannelHandlerContext写入缓冲区,write()方法将把缓冲区数据发送到下一个 ChannelHandler
ctx.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));
}
异常
异常处理是任何真实应用程序的重要组成部分,它也可以通过多种方式来实现。因此, Netty提供了几种方式用于处理入站或者出站处理过程中所抛出的异常。
入站异常
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
- hannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给ChannelPipeline 中的下一个 ChannelHandler;
- 如果异常到达了 ChannelPipeline 的尾端,它将会被记录为未被处理;
- 要想定义自定义的处理逻辑,你需要重写 exceptionCaught()方法。然后你需要决定是否需要将该异常传播出去。
出站异常
用于处理出站操作中的正常完成以及异常的选项, 都基于以下的通知机制。
- 每个出站操作都将返回一个 ChannelFuture。 注册到 ChannelFuture 的 ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了。
- 几乎所有的 ChannelOutboundHandler 上的方法都会传入一个 ChannelPromise的实例。作为 ChannelFuture 的子类, ChannelPromise 也可以被分配用于异步通知的监听器。
public ChannelFuture write(Object msg);
public void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise);