前面两篇讲解了ChannelPipeline
ChannelPipeline的功能,是用来管理Channelhandler,和拦截事件请求
并且分析了Netty的核心类DefaultChannelPipeline.
具体内容可以参考
「Netty核心技术」6-ChannelPipeline源码
「Netty核心技术」-ChannelPipeline源码2
今天来分析ChannelHandler的原理
同样源码分析的步骤,请参考这份源码阅读步骤你值得拥有
一、ChannelHandler的功能
ChannelHandler类似于Servlet的Filter过滤器,负责对I/O事件或者I/O操作进行拦截和处理,它可以选择性的拦截和处理自己刚兴趣的事件,也可以透传和终止事件的传递。
基于ChannelHandler接口,用户可以方便的进行业务逻辑定制,例如打印日志、统一封装异常信息、性能统计和消息编解码等等。
- 负责I/O事件的拦截和处理
- 可选择性的拦截
- 可以透传
- 可以终止事件的传播
二、熟悉ChannelHandler的使用
这一步骤我打算留在后面写一个例子,具体讲解,前面也写过一个helloworld,大家可参考下「Netty核心技术」2-HelloWorld
三、ChannelHandler的相关核心类
1.分析Channelhandler
提供了三个方法和一个注解,如下
public interface ChannelHandler { //handler被添加后的回调处理 void handlerAdded(ChannelHandlerContext ctx) throws Exception; //handler被移除后的回调处理 void handlerRemoved(ChannelHandlerContext ctx) throws Exception; //异常处理 @Deprecated void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; //是否单个ChannelHandler能够被多个ChannelPipeline共享 @Inherited @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Sharable { // no value }}
ChannelHandler提供了ChannelHandler添加移除后的回调操作,但是没有I/O相关的其他事件处理方法。
2.分析ChannelHandlerAdapter
public abstract class ChannelHandlerAdapter implements ChannelHandler
实现了ChannelHandler。
参数:added表示该ChannelHandler是否已经被添加到ChannelPineline中去了。
核心方法:ensureNotSharable确保该Handler不是共享,isShareable()判断该Handler能否被共享。
/** * Do nothing by default, sub-classes may override this method. */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // NOOP } /** * Do nothing by default, sub-classes may override this method. */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // NOOP } /** * Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward * to the next {@link ChannelHandler} in the {@link ChannelPipeline}. * * Sub-classes may override this method to change behavior. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); }
Channelhandler的基础三个方法,默认实现,什么也不用做,因为大部分情况下我们是不用实现这个方法的。
ChannelHandlerAdapter实现了ChannelHandler,默认实现了Channelhandler的基本方法。我们进行扩展的时候,可以继承这个类。
3.分析ChannelInboundHandler类,ChannelOutboundHandler类
关于这两个类,分别对应的是上两篇讲过的inbound事件和outbound事件。具体的使用说明和ChannelInboundInvoker,ChannelOutboundInvoker我贴下他们的说明
ChannelInboundInvoker类
public interface ChannelInboundInvoker { //Channel注册事件 //Channel注册成功后会执行该方法,对应的地方AbstractChannel.register0方法 ChannelInboundInvoker fireChannelRegistered(); //取消注册事件 //取消注册后调用,对应的地方AbstractChannel.deregister ChannelInboundInvoker fireChannelUnregistered(); //TCP链路建立成功,Channel激活事件,客户端与服务端建立连接 //客户端发起请求后 //服务端会从ServerBootstrapAcceptor会收到channelRead,childGroup注册一个child //对应的地方是AbstractChannel.register0 //客户端则再连接成功后对应的地方AbstractNioChannel.fulfillConnectPromise ChannelInboundInvoker fireChannelActive(); //客户端与服务端断开连接的时候调用 ChannelInboundInvoker fireChannelInactive(); //异常的通知事件 ChannelInboundInvoker fireExceptionCaught(Throwable cause); //用户自定义事件 //就是我们定义ChannelHandler的时候可以调用我们自定义的fireUserEventTriggered ChannelInboundInvoker fireUserEventTriggered(Object event); //读数据事件 //对应调用的地方是NioUnsafe.read ChannelInboundInvoker fireChannelRead(Object msg); //读操作完成 //对应调用的地方是NioUnsafe.read读完后调用 ChannelInboundInvoker fireChannelReadComplete(); //channel的可写状态变化通知事件 ChannelInboundInvoker fireChannelWritabilityChanged();}
ChannelOutboundInvoker类
public interface ChannelOutboundInvoker { //bind本地地址事件 ChannelFuture bind(SocketAddress localAddress); //连接服务端事件 ChannelFuture connect(SocketAddress remoteAddress); ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress); //断开连接事件 ChannelFuture disconnect(); //关闭当前channel事件 ChannelFuture close(); //取消注册事件 ChannelFuture deregister(); ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise); ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise); ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); ChannelFuture disconnect(ChannelPromise promise); ChannelFuture close(ChannelPromise promise); ChannelFuture deregister(ChannelPromise promise); //读事件 ChannelOutboundInvoker read(); //发送事件 ChannelFuture write(Object msg); ChannelFuture write(Object msg, ChannelPromise promise); //刷新事件 ChannelOutboundInvoker flush(); //写和刷新事件 ChannelFuture writeAndFlush(Object msg, ChannelPromise promise); ChannelFuture writeAndFlush(Object msg); ChannelPromise newPromise(); ChannelProgressivePromise newProgressivePromise(); ChannelFuture newSucceededFuture(); ChannelFuture newFailedFuture(Throwable cause); ChannelPromise voidPromise();}
ChannelInboundHandler对应的是inbound事件也就是:
发生某个网络I/O事件,从Socket.read开始的TCP链路建立事件,链路关闭事件,读事件,异常通知事件等等事件,对应的是inbound事件,从head开始,一直执行到tail。
ChannelOutboundHandler对应是outbound事件也就是:
从ChannelHandlerContext开始的由用户线程或者代码发起的是事件,例如用户发起的连接操作,绑定操作,消息发送等操作,对应的是outbound事件,从tail开始,一直执行到head,并且调用Socket.write()。
举个例子我们编解码,有编码和解码两种,他们是如何调用的呢?
我们本地I/O线程处理好数据后,开始编码,然后一些列的handler处理,处理完后。其他服务接收到,开始解码等等。
看图。
相信大家看了这幅度,就明白了。网络读取事件后,ChannelHandler从头部到尾部依次解码,当写入网络的时候从tail到head一次编码。
编码N、N-1、...一直到head。
解码head、1....一直到Tail。刚好对应起来。
4.分析ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类
我们发现这两个类就只是做了一件事情,默认实现,因为我们并不是需要对所有的功能进行拦截处理,只需要指定个别的我们所需要的,所以整个Adapter就做了这个功能。
在实际使用中,我们也只需要继承这个类就可以了。
回到上面
我们可以随意拦截自己喜欢的事件,就是只要继承ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类,然后实现我们需要的方法就可以了。
那什么什么叫做透传?
透传就是可以继续往下面的handler进行传播处理。
拿如下例子来说
public class TimeClientHanlder extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { byte[] bytes = "Ping...".getBytes(); ByteBuf byteBuf = Unpooled.buffer(bytes.length); byteBuf.writeBytes(bytes); ctx.writeAndFlush(byteBuf); ctx.fireChannelActive(); }}
客户端与服务端TCP建立连接后,有一个激活事件channelActive,客户端先发送一段消息给服务端端,随即调用了ctx.fireChannelActive();
这个ctx.fireChannelActive();就是透传,handler能够沿着这个继续往下面的Handler走下去。
透传有两种实现
第一种
ctx.fireChannelActive();
第二种
super.channelActive(ctx);
那什么叫做终止传播呢?
终止传播就是不使用上面的透传就可以了。
public class TimeClientHanlder extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { byte[] bytes = "Ping...".getBytes(); ByteBuf byteBuf = Unpooled.buffer(bytes.length); byteBuf.writeBytes(bytes); ctx.writeAndFlush(byteBuf); }}
当时间执行到这个Handler的时候就被终止了。
总结:
本篇讲解了Netty的核心类ChannelHandler,它的默认实现ChannelHandlerAdapter,以及对应的inbound事件处理器ChannelInboundHandlerAdapter,outbound事件处理器ChannelOutboundHandlerAdapter。
我们自定义事件只要继承这个两个Adapter就可以了。
特别是取了一个编解码的列子。
编码,一定是我们对数据进行封装,要进行socket.write的事件,那么对应的是outbound事件,需要继承ChannelOutboundHandlerAdapter。
解码,是从网络读取Socket.read,读取的事件,对应inbound事件,需要继承ChannelInboundHandlerAdapter。