netty理论之源码分析④ ChannelHandler

ChannelHandler是啥?

   netty为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。举个栗子 ChannelInboundHandler,一个最常用的Handler。这个Handler的作用就是处理接收到数据时的事件,也就是说,我们的业务逻辑一般就是写在这个Handler里面的,ChannelInboundHandler就是用来处理我们的核心业务逻辑。

ChannelHandler是Netty中负责处理Channel上的IO事件、其他事件的组件,例如处理连接建立、收到特定消息特定事件等。ChannelHandler通过处理事件类型的不同又可以分为ChannelInboundHandler和ChannelOutboundHandler。一般当我们实现的是一个服务器时,我们将服务器server想象为靠近自己的一端,Client为远离自己的一端。这样Inbound就是进来的事件,一般为Channel已注册、Channel读到消息、Channel特定用户事件触发。而Outbound事件则为从Server发向Client的事件,例如write、flush等。当实现的是Client端时也是类似的道理,站在自己的角度看进来和出去的数据事件即可。 常见的ChannelHandler分为编解码器Codec、业务事件处理等。 Codec分为Encoder和Decoder 编码和解码器,因为网络传输的都是二进制数据而应用程序需要使用特定的结构对象,所以需要进行互相的转换。转换成业务对象后由应用自定义的业务时间处理器来处理。

ChannelHandler代码分析

每个Channel在创建后都会有一个ChannelPipeline, ChannelPipeline上是绑定到当前Channel的ChannelHandle的列表,这个是可以在运行过程中随时增加和减少ChannelHandler的。

ChannelHandler定义了channelAdd, channelRemove表示当前ChannelHandler增加和移除时候的回调。

/**

    * Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.

    */

   void handlerAdded(ChannelHandlerContext ctx) throws Exception;



   /**

    * Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events

    * anymore.

    */

   void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

ChannelInboundHandler拦截和处理入站事件,ChannelOutboundHandler拦截和处理出站事件。ChannelHandler和ChannelHandlerContext通过组合或继承的方式关联到一起成对使用。事件通过ChannelHandlerContext主动调用如fireXXX()和write(msg)等方法,将事件传播到下一个处理器。注意:入站事件在ChannelPipeline双向链表中由头到尾正向传播,出站事件则方向相反。当客户端连接到服务器时,Netty新建一个ChannelPipeline处理其中的事件,而一个ChannelPipeline中含有若干ChannelHandler。如果每个客户端连接都新建一个ChannelHandler实例,当有大量客户端时,服务器将保存大量的ChannelHandler实例。为此,Netty提供了Sharable注解,如果一个ChannelHandler状态无关,那么可将其标注为Sharable,如此,服务器只需保存一个实例就能处理所有客户端的事件。

@Sharable

添加ChannelHandler可以通过ChannelPipeline的addFirst、AddLast等方法来添加,这里涉及到一个共享并发相关的问题,当ChannelHandler保存了一个实例变量,这个实例变量是和单独Channel相关的时,如果这个ChannelHandler被多个ChannelPipeline共享了就可能出现并发问题,例如一个是否已经登录的状态,这种情况有几种解决方式

  1. 每个ChannelPipeline使用单独的ChannelHandler,而前面章节介绍的一个Channel只会有一个线程来处理它的IO相关事件所以是线程安全的。

  2. ChannelHandler不保存实例变量或者保存的是线程安全的变量并且通过合理的同步控制并发。如果不保存实例变量,可以通过一个ChannelHandlerContext来保存,ChannelHandlerContext可以理解为ChannelPipeline和ChannelHandler结合的对象,ChannelHandlerContext可以通过AttributeMap来保存当前Channel相关的信息,用于前后传递。 在后面的文章中会继续介绍更多的Channel、ChannelPipelie、ChannelHandler等信息,并且会总结Netty的线程模型等

分析解读一波常用的ChannelHandler

  1. ChannelPipeline 
  2. ChannelHandlerContext 
  3. ChannelHandler 
  4. Inbound vs outbound(入站和出站

1.ChannelPipeline(贯穿netty的大动脉)

首先介绍一下Channel和ChannelPipeline关系

相信大家都知道了, 在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系如下:

clipboard.png

通过上图我们可以看到, 一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表. 这个链表的头是 HeadContext, 链表的尾是 TailContext, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler.

ChannelPipeline是ChannelHandler实例的列表(或则说是容器),用于处理或截获通道的接收和发送数据。ChannelPipeline提供了一种高级的截取过滤器模式,让用户可以在ChannelPipeline中完全控制一个事件及如何处理ChannelHandler与ChannelPipeline的交互。

可以这样说,一个新的通道就对应一个新的ChannelPipeline并附加至通道。一旦连接,通道Channel和ChannelPipeline之间的耦合是永久性的。通道Channel不能附加其他的ChannelPipeline或从ChannelPipeline分离

下图显示了ChannelHandler在ChannelPipeline中的IO处理示意图: 
这里写图片描述

很明显,ChannelPipeline里面就是一个ChannelHandler的列表。如果一个入站IO事件被触发,这个事件会从第一个开始依次通过ChannelPipeline中的ChannelHandler。若是一个入站I/O事件,则会从最后一个开始依次通过ChannelPipeline中的ChannelHandler。ChannelHandler可以处理事件并检查类型,如果某个ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandler。ChannelPipeline可以动态添加、删除、替换其中的ChannelHandler,这样的机制可以提高灵活性。

修改ChannelPipeline的方法: 
• addFirst(…),添加ChannelHandler在ChannelPipeline的第一个位置 
• addBefore(…),在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler 
• addAfter(…),在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler 
• addLast(ChannelHandler…),在ChannelPipeline的末尾添加ChannelHandler 
• remove(…),删除ChannelPipeline中指定的ChannelHandler 
• replace(…),替换ChannelPipeline中指定的ChannelHandler

ChannelPipeline pipeline = ch.pipeline();  
FirstHandler firstHandler = new FirstHandler();  
pipeline.addLast("handler1", firstHandler);  
pipeline.addFirst("handler2", new SecondHandler());  
pipeline.addLast("handler3", new ThirdHandler());  
pipeline.remove("“handler3“");  
pipeline.remove(firstHandler);  
pipeline.replace("handler2", "handler4", new FourthHandler());  

关于ChannelPipeline更多更加具体的介绍可以访问如下文章https://segmentfault.com/a/1190000007308934https://www.cnblogs.com/wade-luffy/p/6217912.html

2.ChannelHandlerContext

每个ChannelHandler被添加到ChannelPipeline后,都会创建一个ChannelHandlerContext并与之创建的ChannelHandler关联绑定。ChannelHandlerContext允许ChannelHandler与其他的ChannelHandler实现进行交互。ChannelHandlerContext不会改变添加到其中的ChannelHandler,因此它是安全的。下图显示了ChannelHandlerContext、ChannelHandler、ChannelPipeline的关系:

ChannelHandlerContext可以通知下一个ChannelHandler

如果我们想有一些事件流全部通过ChannelPipeline,有两个不同的方法可以做到: 
• 调用Channel的方法 
• 调用ChannelPipeline的方法

这两个方法都可以让事件流全部通过ChannelPipeline。无论从头部还是尾部开始,因为它主要依赖于事件的性质。如果是一个“入站”事件,它开始于头部;若是一个“出站”事件,则开始于尾部。

下面的代码显示了一个写事件如何通过ChannelPipeline从尾部开始:

protected void initChannel(SocketChannel ch) throws Exception {  
    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {  
        @Override  
        public void channelActive(ChannelHandlerContext ctx) throws Exception {  
            //Event via Channel  
            Channel channel = ctx.channel();  
            channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));  

            //Event via ChannelPipeline  
            ChannelPipeline pipeline = ctx.pipeline();  
            pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));  
        }  
    });  
}  

下图表示通过Channel或ChannelPipeline的通知: 
这里写图片描述

ChannelHandlerContext可以修改ChannelPipeline

调用ChannelHandlerContext的pipeline()方法能访问ChannelPipeline,能在运行时动态的增加、删除、替换ChannelPipeline中的ChannelHandler。我们可以保持ChannelHandlerContext以供以后使用,如外部Handler方法触发一个事件,甚至从一个不同的线程触发。 
下面代码显示了保存ChannelHandlerContext供之后使用或其他线程使用:

public class WriteHandler extends ChannelHandlerAdapter {
    private ChannelHandlerContext ctx;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
    }

    public void send(String msg){
        ctx.write(msg);
    }
}

请注意,ChannelHandler实例如果带有@Sharable注解则可以被添加到多个ChannelPipeline。也就是说单个ChannelHandler实例可以有多个ChannelHandlerContext,因此可以调用不同ChannelHandlerContext获取同一个ChannelHandler。如果添加不带@Sharable注解的ChannelHandler实例到多个ChannelPipeline则会抛出异常;使用@Sharable注解后的ChannelHandler必须在不同的线程和不同的通道上安全使用。怎么是不安全的使用?看下面代码:

@Sharable
public class NotSharableHandler extends ChannelInboundHandlerAdapter {

    private int count;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        count++;
        System.out.println("channelRead(...) called the " + count + " time“");
        ctx.fireChannelRead(msg);
    }

}

上面是一个带@Sharable注解的Handler,它被多个线程使用时,里面count是不安全的,会导致count值错误。 
为什么要共享ChannelHandler?使用@Sharable注解共享一个ChannelHandler在一些需求中还是有很好的作用的,如使用一个ChannelHandler 本章参考于文章 https://blog.csdn.net/u010853261/article/details/54574440

另外,ChannelHandlerContext的writeAndFlush方法会将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理。ChannelHandlerContext#writeAndFlush实现源码:

private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }

Channel的writeAndFlush方法会将数据写到ChannelPipeline中最后一个ChannelHandler然后数据从尾部开始向头部方向流动会经过所有的ChannelHandler, ChannelPipeline中的所有ChannelHandler都可以处理数据。

public final ChannelFuture writeAndFlush(Object msg) {
        return tail.writeAndFlush(msg);
    }

3. channel的状态模型

Netty有一个简单但强大的状态模型,并完美映射到ChannelInboundHandler的各个方法。下面是Channel生命周期四个不同的状态: 
1. channelUnregistered 
2. channelRegistered 
3. channelActive 
4. channelInactive

Channel的状态在其生命周期中变化,因为状态变化需要触发,下图显示了Channel状态变化: 
这里写图片描述

4、ChannelHandler及其子类

先看一些ChannelHandler的继承图: 
这里写图片描述

1、ChannelHandler中的方法

Netty定义了良好的类型层次结构来表示不同的处理程序类型,所有的类型的父类是ChannelHandler。ChannelHandler提供了在其生命周期内添加或从ChannelPipeline中删除的方法。

1. handlerAdded,ChannelHandler添加到实际上下文中准备处理事件 
2. handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件 
3. exceptionCaught,处理抛出的异常

netty还提供了一个实现了ChannelHandler的抽象类ChannelHandlerAdapter。ChannelHandlerAdapter实现了父类的所有方法,基本上就是传递事件到ChannelPipeline中的下一个ChannelHandler直到结束。我们也可以直接继承于ChannelHandlerAdapter,然后重写里面的方法。

2、ChannelInboundHandler

ChannelInboundHandler提供了一些方法再接收数据或Channel状态改变时被调用。下面是ChannelInboundHandler的一些方法: 
1. channelRegistered,ChannelHandlerContext的Channel被注册到EventLoop; 
2. channelUnregistered,ChannelHandlerContext的Channel从EventLoop中注销 
3. channelActive,ChannelHandlerContext的Channel已激活 
4. channelInactive,ChannelHanderContxt的Channel结束生命周期 
5. channelRead,从当前Channel的对端读取消息 
6. channelReadComplete,消息读取完成后执行 
7. userEventTriggered,一个用户事件被处罚 
8. channelWritabilityChanged,改变通道的可写状态,可以使用Channel.isWritable()检查 
9. exceptionCaught,重写父类ChannelHandler的方法,处理异常

netty提供了一个实现了ChannelInboundHandler接口并继承ChannelHandlerAdapter的类:ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter实现了ChannelInboundHandler的所有方法,作用就是处理消息并将消息转发到ChannelPipeline中的下一个ChannelHandler。ChannelInboundHandlerAdapter的channelRead方法处理完消息后不会自动释放消息,若想自动释放收到的消息,可以使用SimpleChannelInboundHandler

看下面的代码:

/**
 * 实现ChannelInboundHandlerAdapter的Handler,不会自动释放接收的消息对象
 * @author c.k
 *
 */
public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //手动释放消息
        ReferenceCountUtil.release(msg);
    }
}

SimpleChannelInboundHandler会自动释放消息

/**
 * 继承SimpleChannelInboundHandler,会自动释放消息对象
 * @author c.k
 *
 */
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        //不需要手动释放
    }
}

ChannelInitializer用来初始化ChannelHandler,将自定义的各种ChannelHandler添加到ChannelPipeline中。

猜你喜欢

转载自blog.csdn.net/weixin_39666581/article/details/81154888