从NIO到netty(11)ChannelHandler和ChannelHandlerContext

56 篇文章 2 订阅

上一篇我们分析了ChannelInitializer加入到channelpipeline中,那么具体是如何加入到channelpipeline中的呢?

要解释这个必须先说明几个的东西

Channel的生命周期:

Channel 接口定义了四个方法,分别代表了Channel生命周期中回调的方法:

ChannelUnregistered Channel 已经被创建,但还未注册到 EventLoop

ChannelRegistered Channel 已经被注册到了 EventLoop

ChannelActive Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了

ChannelInactive Channel没有连接到远程节点

 ChannelHandler

上一篇摘录的ChannelPipeline已经解释说明了ChannelHandler和ChannelPipeline的关系

ChannelHandler接口也有三个方法定义了处于该状态下的回调方法

handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught 当处理过程中在 ChannelPipeline 中有错误产生时被调用

Netty 定义了下面两个重要的 ChannelHandler 子接口:

ChannelInboundHandler——处理入站数据以及各种状态变化;

ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

先看下ChannelInboundHandler接口中定义的方法:

public interface ChannelInboundHandler extends ChannelHandler {

    //当channel已经注册到它的EventLoop并且能够处理I/O时被调用
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;
    
//当Channel从它的EventLoop注销并且无法处理任何I/O时被调用
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    //Channel出于活动状态。就是连接建立
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    //连接中断
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    //当从channel读取数据时被调用
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    //当channel上的一个读操作完成时
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if an user event was triggered.
     */
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    /**
     * Gets called once the writable state of a {@link Channel} changed. You can check the state with
     * {@link Channel#isWritable()}.
     */
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    /**
     * Gets called if a {@link Throwable} was thrown.
     */
    @Override
    @SuppressWarnings("deprecated")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

如果你想自己写一个ChannelInboundHandler的实现类,但是只想重写ChannelInboundHandler接口中的部分方法,可以直接继承ChannelInboundHandlerAdapter。最典型的就是SimpleChannelInboundHandler

接下去说一下ChannelOutboundHandler

public interface ChannelOutboundHandler extends ChannelHandler {
    
//当请求将Channel绑定到本地时调用
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
    
//当请求将Channel连接到远程节点时被调用
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    //当请求将Channel从远程节点断开时候调用
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    //当关闭请求时调用
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

   //channel从EventLoop注销时调用
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

   //当请求从Channel读取更多数据时被调用
    void read(ChannelHandlerContext ctx) throws Exception;

   //当请求通过channel写入到远程节点调用
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    //当请求通过 Channel 将入队数据冲刷到远程
节点时被调用
    void flush(ChannelHandlerContext ctx) throws Exception;

接下去说明一下ChannelHandlerContext

ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关 联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandler- Context。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在 同一个 ChannelPipeline 中的其他 ChannelHandler 之间的交互。

下面一张表介绍了ChannelHandlerContext接口中的方法和作用

下面一张图展示了Channel、ChannelHandler以及ChannelHandlerContext之间的关系

我们回到DefaultChannelPipeline#addLast方法中

找到几个重要方法
newCtx = newContext(group, filterName(name, handler), handler);

---->

return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);

直接进入DefaultChannelHandlerContext的构造函数

private final ChannelHandler handler;

DefaultChannelHandlerContext(
        DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
    super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    this.handler = handler;
}

这里可以看到DefaultChannelHandlerContext持有handler的一个引用,也就是就是说一个handler对应一个context

进入父类AbstractChannelHandlerContext的构造函数

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                              boolean inbound, boolean outbound) {
    this.name = ObjectUtil.checkNotNull(name, "name");
    this.pipeline = pipeline;
    this.executor = executor;
    this.inbound = inbound;//判断是否是入站处理器
    this.outbound = outbound;//判断是否是出站处理器
    // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}
addLast0(newCtx);

----->

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

这段代码很简单,就是一个链表的重构,实际上就是把这个context加入到尾节点之前

接着走代码

如果registered是false,意味着channel没有在事件循环组中注册过,
            if (!registered) {
                newCtx.setAddPending();//将当前context挂起。
                callHandlerCallbackLater(newCtx, true);//建立一个线程任务稍后执行。
                return this;
            }

callHandlerCallbackLater--->

assert !registered;

PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
    pendingHandlerCallbackHead = task;
} else {
    //将新建的任务添加到链表里边
    while (pending.next != null) {
        pending = pending.next;
    }
    pending.next = task;
}

 

回到addLast0方法

 EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        //我们自己重写的handler的handlerAdded方法会被执行。
        callHandlerAdded0(newCtx);
        return this;

到这里为止,对addLast的流程已经比较清楚了,

找到ChannelInitailizer#initChannel方法

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
            // We do so to prevent multiple calls to initChannel(...).
            exceptionCaught(ctx, cause);
        } finally {
            remove(ctx);
        }
        return true;
    }
    return false;

这里面注意会有一个 remove(ctx); 也就是说在调用完自定义的ChannelInitailizer的initChannel方法之后会把ChannelInitailizer从pipeline中删除。。

-----

在这里说一个小知识。

我们知道Channel和ChannelHandlerContext都继承了AttributeMap接口,在netty4.0时期,channel和channelHandlerContext的attr方法是获取的值时不一样的,netty官方时这么解释的

Channel 和ChannelHandlerContext都 实现了AttributeMap 用来设置用户自定义的属性。有时候Channel 和ChannelHandlerContext 都有自己的一套用户定义的属性(相互之间没有任何关系,即Channel 有自己的map,ChannelHandlerContext 也有自己的map)让用户感到非常困惑,比如我们使用 Channel.attr(KEY_X).set(valueX)设置一个key和value,但是没法通过ChannelHandlerContext.attr(KEY_X).get()方式获得,而且这样还浪费内存。
于是到了4.1就有了改动

为了解决这个问题,我们决定在每个Channel 内部只维护一个map,AttributeMap 永远使用AttributeKey 作为他的key,AttributeKey 保证在所有key之中是唯一的,这样就没有必要每个Channel定义多个属性,这样每个用户在ChannelHandler里边定义私有的静态属性的key(AttributeKey )就没有重复键的问
 

我们找到AbstractChannelHandlerContext#attr方法

@Override
public <T> Attribute<T> attr(AttributeKey<T> key) {
    return channel().attr(key);
}

可以看到直接调用channel的attr方法

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值