前面两篇讲解了ChannelPipeline

ChannelPipeline的功能,是用来管理Channelhandler,和拦截事件请求

并且分析了Netty的核心类DefaultChannelPipeline.

具体内容可以参考

「Netty核心技术」6-ChannelPipeline源码

「Netty核心技术」-ChannelPipeline源码2

今天来分析ChannelHandler的原理

同样源码分析的步骤,请参考这份源码阅读步骤你值得拥有

一、ChannelHandler的功能

ChannelHandler类似于Servlet的Filter过滤器,负责对I/O事件或者I/O操作进行拦截和处理,它可以选择性的拦截和处理自己刚兴趣的事件,也可以透传和终止事件的传递。

基于ChannelHandler接口,用户可以方便的进行业务逻辑定制,例如打印日志、统一封装异常信息、性能统计和消息编解码等等。

  1. 负责I/O事件的拦截和处理
  2. 可选择性的拦截
  3. 可以透传
  4. 可以终止事件的传播

二、熟悉ChannelHandler的使用

这一步骤我打算留在后面写一个例子,具体讲解,前面也写过一个helloworld,大家可参考下「Netty核心技术」2-HelloWorld

三、ChannelHandler的相关核心类




nettyt NioSocketChannel 断开判断 netty 断开连接_服务端


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处理,处理完后。其他服务接收到,开始解码等等。

看图。


nettyt NioSocketChannel 断开判断 netty 断开连接_核心技术_02


相信大家看了这幅度,就明白了。网络读取事件后,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。