Netty-原理篇(ChannelPipeline&ChannelHandler)

      Netty的Channel过滤器实现原理与Servlet、Filter机制一致,它将Channel的数据管道抽象为ChannelPipeline消息在ChannelPipeline中流动和传递。ChannelPipeline 持有I/O事件拦截器ChannelHandler的链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便地通过新增和删除ChannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。

整体概况

下图展示了具体的事件传播流程图:

  • (1)底层的SocketChannel read()方法读取ByteBuf, 触发ChannelRead事件,由I/O线程NioEventLoop 调用ChannelPipeline 的fireChannelRead(Object msg)方法,将消息( ByteBuf)传输到ChannelPipeline中。

  • (2)消息依次被HeadHandler、ChannelHandlerl 、ChannelHandler2-.. ... TailHandler拦截和处理,在这个过程中,任何ChannelHandler都可以中断当前的流程,结束消息的传递;

  • (3)调用ChannelHandlerContext的write方法发送消息,消息从TailHandler开始,途经ChannelHanderN.....ChannelHandlerl. HeadHandler, 最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的Future返回。

 *                                                 I/O Request
 *                                            via {@link Channel} or
 *                                        {@link ChannelHandlerContext}
 *                                                      |
 *  +---------------------------------------------------+---------------+
 *  |                           ChannelPipeline         |               |
 *  |                                                  \|/              |
 *  |    +---------------------+            +-----------+----------+    |
 *  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  |               |                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  .               |
 *  |               .                                   .               |
 *  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
 *  |        [ method call]                       [method call]         |
 *  |               .                                   .               |
 *  |               .                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  |               |                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  +---------------+-----------------------------------+---------------+
 *                  |                                  \|/
 *  +---------------+-----------------------------------+---------------+
 *  |               |                                   |               |
 *  |       [ Socket.read() ]                    [ Socket.write() ]     |
 *  |                                                                   |
 *  |  Netty Internal I/O Threads (Transport Implementation)            |
 *  +-------------------------------------------------------------------+

先理清一个关系:
一个Channel 包含了一个ChannelPipeline , 而ChannelPipeline 中又维护了一个由ChannelHandlerContext 组成的双向链表。这个链表的头是HeadContext,链表的尾是TailContext,并且每个ChannelHandlerContext 中又关联着一个ChannelHandler(这个channelHandlerContext只是channelHandler的上下文环境,用来保存上下文环境以及和前后channelHandler做一个桥梁沟通),如下图:

组件介绍

Channel

Channel 概念与 java.nio.channel 概念一致, 用以连接IO设备 (socket, 文件等) 的纽带. 例如将网络的读、写, 客户端发起连接, 主动关闭连接, 链路关闭, 获取通信双方的网络地址等.

Channel 的 IO 类型主要有两种: 非阻塞IO (NIO) 以及阻塞IO(OIO).

数据传输类型有两种: 按事件消息传递 (Message) 以及按字节传递 (Byte).

适用方类型也有两种: 服务器(ServerSocket) 以及客户端(Socket). 还有一些根据传输协议而制定的的Channel, 如: UDT、SCTP等.

Netty 按照类型逐层设计相应的类. 最底层的为抽象类 AbstractChannel, 再以此根据IO类型、数据传输类型、适用方类型实现. 类图可以一目了然, 如下图所示:

 Channel 状态

channelRegistered 状态 

/**
 * The {@link Channel} of the {@link ChannelHandlerContext} was registered with its {@link EventLoop}
 */
void channelRegistered(ChannelHandlerContext ctx) throws Exception;

从注释里面可以看到是在 Channel 绑定到 Eventloop 上面的时候调用的.

不管是 Server 还是 Client, 绑定到 Eventloop 的时候, 最终都是调用 Abstract.initAndRegister() 这个方法上(Server是在 AbstractBootstrap.doBind() 的时候调用的, Client 是在 Bootstrap.doConnect() 的时候调用的).

initAndRegister() 方法定义如下:

final ChannelFuture initAndRegister() {
    final Channel channel = channelFactory().newChannel();
    try {
        init(channel);
    } catch (Throwable t) {
        channel.unsafe().closeForcibly();
        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
        return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
    }
    // 把channel绑定到Eventloop对象上面去
    ChannelFuture regFuture = group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

继续跟踪下去会定位到 AbstractChannel.AbstractUnsafe.register0() 方法上.

 private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                // 做实际的绑定动作。把Channel感兴趣的事件注册到Eventloop.selector上面.具体实现在Abstract.doRegister()方法内
                doRegister();
                neverRegistered = false;
                registered = true;

                // 通过pipeline的传播机制,触发handlerAdded事件
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                
                // 通过pipeline的传播机制,触发channelRegistered事件
                pipeline.fireChannelRegistered();

                // 还没有绑定,所以这里的 isActive() 返回false.
                if (isActive()) {
                    if (firstRegistration) {
                        // 如果当前链路已经激活,则调用channelActive()方法
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

从上面的代码也可以看出, 在调用完 pipeline.fireChannelRegistered() 之后, 紧接着会调用 isActive() 判断当前链路是否激活, 如果激活了则会调用 pipeline.fireChannelActive() 方法.

这个时候, 对于 Client 和 Server 都还没有激活, 所以, 这个时候不管是 Server 还是 Client 都不会调用 pipeline.fireChanenlActive() 方法.

channelActive 状态

从启动器的 bind() 接口开始, 往下调用 doBind() 方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 初始化及注册
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        // 调用 doBind0
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        ....
    }
}

doBind 方法又会调用 doBind0() 方法, 在 doBind0() 方法中会通过 EventLoop 去执行 channel 的 bind()任务.

private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {

    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                // 调用channel.bind接口
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

doBind0() 方法往下会调用到 pipeline.bind(localAddress, promise); 方法, 通过 pipeline 的传播机制, 最终会调用到 AbstractChannel.AbstractUnsafe.bind() 方法, 这个方法主要做两件事情:

  • 调用 doBind(): 调用底层JDK API进行 Channel 的端口绑定.
  • 调用 pipeline.fireChannelActive().
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    
    ....
    
    // wasActive 在绑定成功前为 false
    boolean wasActive = isActive();
    try {
        // 调用doBind()调用JDK底层API进行端口绑定
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    // 完成绑定之后,isActive() 返回true
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                // 触发channelActive事件
                pipeline.fireChannelActive();
            }
        });
    }
    safeSetSuccess(promise);
}

也就是说当有新客户端连接的时候, 会变成活动状态.

channelInactive 状态

fireChannelnactive() 方法在两个地方会被调用: Channel.close() 和 Channel.disconnect().

在调用前会先确认状态是从 Active--->Inactive.

channelUnregistered 状态

fireChannelUnregistered() 方法是在 Channel 从 Eventloop 中解除注册的时候被调用的. Channel.close() 的时候被触发执行.

ChannelHandler

ChannelHandler类似于Servlet的Filter 过滤器,负责对I/O事件或者I/O操作进行拦截和处理,它可以选择性地拦截和处理自己感兴趣的事件,也可以透传和终止事件的传递。基于ChannelHandler接口,用户可以方便地进行业务逻辑定制,例如打印日志、统一封装异常信息、性能统计和消息编解码等。

ChannelHandler支持注解,目前支持的注解有两种。

  • Sharable: 多个ChannelPipeline共用同一个ChannelHandler;
  • Skip:被Skip注解的方法不会被调用,直接被忽略。

ChannelHandler的生命周期

handlerAdded(): 添加到 ChannelPipeline 时调用.
handlerRemoved(): 从 ChannelPipeline 中移除时调用.
exceptionCaught(): 处理过程中在 ChannelPipeline 中有错误产生时调用.

处理 I/O 事件或截获 I/O 操作, 并将其转发到 ChannelPipeline 中的下一个处理程序. ChannelHandler 本身不提供许多方法, 但通常必须实现其子类型之一:

  • ChannelInboundHandler: 处理入站数据以及各种状态变化.
  • ChannelOutboundHandler: 处理出站数据并且允许拦截所有的操作.

ChannelInboundHandler 接口

channelRegistered(): 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用.
channelUnregistered(): 当 Channel 从他的 EventLoop 注销并且无法处理任何 I/O 时被调用.
channelActive(): 当 Channel 处于活动状态时被调用.
channelInactive(): 当 Channel 离开活动状态并且不再连接远程节点时被调用.
channelRead(): 当从 Channel 读取数据时被调用.
channelReadComplete(): 当 Channel 上的一个读操作完成时被调用. 当所有可读字节都从 Channel 中读取之后, 将会调用该回调方法.

ChannelOutboundHandler 接口

出站操作和数据将由 ChannelOutboundHandler 处理. 它的方法将被 Channel ChannelPipeline 以及 ChannelHandlerContext 调用.

ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或事件, 这使得可以通过一些复杂的方法来处理请求. 例如, 如果到远程节点的写入被暂停, 那么你可以推迟刷新操作并在稍后继续.

connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise): 当请求将 Channel 连接到远程节点时被调用.
disconnect(ChannelHandlerContext ctx, ChannelPromise promise): 当请求将 Channel 从远程节点断开时被调用.
deregister(ChannelHandlerContext ctx, ChannelPromise promise): 当请求将 Channel 从它的 EventLoop 注销时被调用.
read(ChannelHandlerContext ctx): 当请求从 Channel 读取更多的数据时被调用.
write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise): 当请求通过 Channel 将数据写到远程节点时被调用.
flush(ChannelHandlerContext ctx): 当请求从 Channel 将入队数据冲刷到远程节点时被调用.

ChannelHandler 适配器

ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 这两个适配器类分别提供了
ChannelInboundHandler 和 ChannelOutboundHandler 的基本实现, 它们继承了共同的父接口
ChannelHandler 的方法, 扩展了抽象类 ChannelHandlerAdapter.

 

ChannelHandlerAdapter 还提供了实用方法 isSharable().

如果其对应的实现被标注为 Sharable, 那么这个方法将返回 true, 表示它可以被添加到多个 ChannelPipeline 中.

ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 中所提供的方法体调用了其相关联的 ChannelHandlerContext 上的等效方法, 从而将事件转发到了 ChannelPipeline 中的 ChannelHandler 中.

Netty提供的ChannelHandler

netty提供了部分handler,开发者可以选择继承实现部分功能
编解码器:ByteToMessageDecoder....
半包处理:DelimiterBasedFrameDecoder,FixedLengthFrameDecoder...

ChannelPromise 和 ChannelFuture

ChannelFuture 表示 Channel 中异步I/O操作的结果, 在 netty 中所有的 I/O 操作都是异步的, I/O 的调用会直接返回, 可以通过 ChannelFuture 来获取 I/O 操作的结果或者状态信息.

当 I/O 操作开始时, 将创建一个新对象. 新的对象是未完成的-它既没有成功, 也没有失败, 也没有被取消, 因为 I/O 操作还没有完成.

如果 I/O 操作已成功完成(失败或取消), 则对象将标记为已完成, 其中包含更具体的信息, 例如故障原因.

请注意, 即使失败和取消属于已完成状态.

ChannelPromise 是 ChannelFuture 的一个子接口, 其定义了一些可写的方法, 如 setSuccess() 和 setFailure(), 从而使 ChannelFuture 不可变.

优先使用addListener(GenericFutureListener),而非await() 

当做了一个 I/O 操作并有任何后续任务的时候, 推荐优先使用 addListener(GenericFutureListener) 的方式来获得通知, 而非 await()

addListener(GenericFutureListener) 是非阻塞的. 它会把特定的 ChannelFutureListener 添加到 ChannelFuture 中, 然后 I/O 线程会在 I/O 操作相关的 future 完成的时候通知监听器.

ChannelFutureListener 会利于最佳的性能和资源的利用, 因为它一点阻塞都没有. 而且不会造成死锁.

ChannelPipeline

通过接口可以发现,channelPipeline主要有两个功能:

  • 第一个管理channelHandler的crud,比如first,last,addxxx等方法
  • 第二个IO事件的传播处理,比如源码中带firexxx的表示触发下一个handler事件

继承图如下:

在前我们已经知道了一个Channel 的初始化的基本过程,下面我们再回顾一下。下面的代码是AbstractChannel 构造器:

protected AbstractChannel(Channel parent, ChannelId id) {
    this.parent = parent;
    this.id = id;
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

HeadContext和TailContext

HeadContext 实现了ChannelInboundHandler和ChannelOutboundHandler,而tail 实现了 ChannelInboundHandler 接口,并且它们都实现了ChannelHandlerContext 接口, 因此可以说head 和tail 即是一个ChannelHandler,又是一个ChannelHandlerContext。接着看HeadContext 构造器中的代码:

final class HeadContext extends AbstractChannelHandlerContext
        implements ChannelOutboundHandler, ChannelInboundHandler {

    private final Unsafe unsafe;

    HeadContext(DefaultChannelPipeline pipeline) {
        super(pipeline, null, HEAD_NAME, HeadContext.class);
        unsafe = pipeline.channel().unsafe();
        setAddComplete();
    }
}

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {

    TailContext(DefaultChannelPipeline pipeline) {
        super(pipeline, null, TAIL_NAME, TailContext.class);
        setAddComplete();
    }

AbstractChannel 有一个pipeline 字段,在构造器中会初始化它为DefaultChannelPipeline 的实例。这里的代码就印证了一点:每个Channel 都有一个ChannelPipeline。接着我们跟踪一下DefaultChannelPipeline 的初始化过程,首先进入到DefaultChannelPipeline 构造器中:

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

在DefaultChannelPipeline 构造器中, 首先将与之关联的Channel 保存到字段channel 中。然后实例化两个ChannelHandlerContext:一个是HeadContext 实例head,另一个是TailContext 实例tail。接着将head 和tail 互相指向, 构成一个双向链表。

特别注意的是:我们在开始的示意图中head 和tail 并没有包含ChannelHandler,这是因为HeadContext 和TailContext继承于AbstractChannelHandlerContext 的同时也实现了ChannelHandler 接口了,因此它们有Context 和Handler的双重属性。

Pipeline的事件传播机制

Netty 中的传播事件可以分为两种:Inbound 事件和Outbound 事件。如下是从Netty 官网针对这两个事件的说明,再次看看图:

从上图可以看出,inbound 事件和outbound 事件的流向是不一样的:

  • inbound 事件的流行是从下至上,并且inbound 的传递方式是通过调用相应的ChannelHandlerContext.fireIN_EVT()方法

    • 例如:ChannelHandlerContext的fireChannelRegistered()调用会发送一个ChannelRegistered 的inbound 给下一个ChannelHandlerContext
  • outbound刚好相反,是从上到下。,而outbound 方法的的传递方式是通过调用ChannelHandlerContext.OUT_EVT()方法。

    • 例如:ChannelHandlerContext 的bind()方法调用时会发送一个bind 的outbound 事件给下一个ChannelHandlerContext。

Outbound 事件传播

outbound 类似于主动触发(发起请求的事件);

Outbound 事件都是请求事件(request event),即请求某件事情的发生,然后通过Outbound 事件进行通知。Outbound 事件的传播方向是tail -> customContext -> head。

ChannelOutboundHandler方法有:

public interface ChannelOutboundHandler extends ChannelHandler {
 void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
 void connect(
 ChannelHandlerContext ctx, SocketAddress remoteAddress,
 SocketAddress localAddress, ChannelPromise promise) throws Exception;
 void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
 void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
 void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
 void read(ChannelHandlerContext ctx) throws Exception;
 void flush(ChannelHandlerContext ctx) throws Exception;
}

 连接事件 案例讲解

我们接下来以connect 事件为例,分析一下Outbound 事件的传播机制。

首先,当用户调用了Bootstrap 的connect()方法时,就会触发一个Connect 请求事件,此调用会触发如下调用链:

 继续跟踪,我们就发现AbstractChannel 的connect()其实由调用了DefaultChannelPipeline 的connect()方法:

@Override
public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
    return tail.connect(remoteAddress, localAddress);
}

可以看到,当outbound 事件(这里是connect 事件)传递到Pipeline 后,它其实是以tail 为起点开始传播的。 而tail.connect()其实调用的是AbstractChannelHandlerContext 的connect()方法:

@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");

    if (isNotValidPromise(promise, false)) {
        // cancelled
        return promise;
    }

    final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null, false);
    }
    return promise;
}

findContextOutbound(MASK_CONNECT)方法顾名思义,它的作用是以当前Context 为起点,向Pipeline 中的Context 双向链表的前端寻找第一个关联ChannelOutboundHandler 的Context,然后返回。 findContextOutbound(MASK_CONNECT)方法代码实现如下:

private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.prev;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
    return ctx;
}

当我们找到了一个outbound 的Context 后,就调用它的invokeConnect()方法,这个方法中会调用Context 其关联的ChannelHandler 的connect()方法:

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
 if (invokeHandler()) {
     try {
         ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);

     } catch (Throwable t) {
       notifyOutboundHandlerException(t, promise);
     }
  } else {
       connect(remoteAddress, localAddress, promise);
  }
}

如果用户没有重写ChannelHandler 的connect()方法,那么会调用ChannelOutboundHandlerAdapter 的connect()实现:

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {

 ctx.connect(remoteAddress, localAddress, promise);
}

我们看到,ChannelOutboundHandlerAdapter 的connect()仅仅调用了ctx.connect(),而这个调用又回到了:Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect这样的循环中,直到connect 事件传递到DefaultChannelPipeline 的双向链表的头节点,即head 中。为什么会传递到head 中呢?回想一下,head 实现了ChannelOutboundHandler。因为head 本身既是一个ChannelHandlerContext,又实现了ChannelOutboundHandler 接口,因此当connect()消息传递到head 后,会将消息转递到对应的ChannelHandler 中处理,而head 的handler()方法返回的就是head 本身:

因此最终connect()事件是在head 中被处理。head 的connect()事件处理逻辑如下:

public void connect(ChannelHandlerContext ctx,SocketAddress remoteAddress, SocketAddress localAddress,ChannelPromise promise) throws Exception {
 //最终调用这里
   unsafe.connect(remoteAddress, localAddress, promise);
}

到这里, 整个connect()请求事件就结束了。下图中描述了整个connect()请求事件的处理过程:

Inbound 事件传播 

inbound 类似于是事件回调(响应请求的事件),

Inbound 的特点是它传播方向是head -> customContext -> tail。

ChannelInboundHandler方法有:

public interface ChannelInboundHandler extends ChannelHandler {
 void channelRegistered(ChannelHandlerContext ctx) throws Exception;
 void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
 void channelActive(ChannelHandlerContext ctx) throws Exception;
 void channelInactive(ChannelHandlerContext ctx) throws Exception;
 void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
 void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
 void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
 void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
 void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

使用fireXXX方法继续传播事件

注意,如果我们捕获了一个事件,并且想让这个事件继续传递下去,那么需要调用Context 对应的传播方法fireXXX

如下面的示例代码:MyInboundHandler 收到了一个channelActive 事件,它在处理后,如果希望将事件继续传播下去那么需要接着调用ctx.fireChannelActive()方法

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
 @Override
 public void channelActive(ChannelHandlerContext ctx) throws Exception {
     System.out.println("连接成功");
     ctx.fireChannelActive();
 }
}

激活事件 案例讲解

Inbound 事件和Outbound 事件的处理过程是类似的,只是传播方向不同。 Inbound 事件是一个通知事件,即某件事已经发生了,然后通过Inbound 事件进行通知。Inbound 通常发生在Channel的状态的改变或IO 事件就绪。

Inbound 的特点是它传播方向是head -> customContext -> tail。

上面我们分析了connect()这个Outbound 事件,那么接着分析connect()事件后会发生什么Inbound 事件,并最终找到Outbound 和Inbound 事件之间的联系。当connect()这个Outbound 传播到unsafe 后,其实是在AbstractNioUnsafe的connect()方法中进行处理的:

public final void connect(final SocketAddress remoteAddress,
 final SocketAddress localAddress, final ChannelPromise promise) {

     if (doConnect(remoteAddress, localAddress)) {
           fulfillConnectPromise(promise, wasActive);
     } else {
         ...
     } 
}

在AbstractNioUnsafe 的connect()方法中,首先调用doConnect()方法进行实际上的Socket 连接,当连接上后会调用fulfillConnectPromise()方法:

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
     if (!wasActive && active) {
           pipeline().fireChannelActive();
     }
}

我们看到,在fulfillConnectPromise()中,会通过调用pipeline().fireChannelActive()方法将通道激活的消息(即Socket 连接成功)发送出去。而这里,当调用pipeline.fireXXX 后,就是Inbound 事件的起点。因此当调用 pipeline().fireChannelActive()后,就产生了一个ChannelActive Inbound 事件,我们就从这里开始看看这个Inbound事件是怎么传播的?

public final ChannelPipeline fireChannelActive() {
     AbstractChannelHandlerContext.invokeChannelActive(head);
     return this;
}

果然, 在fireChannelActive()方法中,调用的是head.invokeChannelActive(),因此可以证明Inbound 事件在Pipeline中传输的起点是head。那么,在head.invokeChannelActive()中又做了什么呢?

static void invokeChannelActive(final AbstractChannelHandlerContext next) {
     EventExecutor executor = next.executor();
     if (executor.inEventLoop()) {
         next.invokeChannelActive();
     } else {
         executor.execute(new Runnable() {
          @Override
          public void run() {
               next.invokeChannelActive();
         }
         });
     }
}

上面的代码应该很熟悉了。回想一下在Outbound 事件(例如connect()事件)的传输过程中时,我们也有类似的操作:

  • 1、首先调用findContextInbound(),从Pipeline 的双向链表中中找到第一个Inbind事件的Context,然后将其返回。

  • 2、调用Context 的invokeChannelActive()方法,invokeChannelActive()方法源码如下:

private void invokeChannelActive() {
   if (invokeHandler()) {
       try {
             ((ChannelInboundHandler) handler()).channelActive(this);
       } catch (Throwable t) {
             notifyHandlerException(t);
       }
   } else {
       fireChannelActive();
   }
}

这个方法和Outbound 的对应方法(如:invokeConnect()方法)如出一辙。与Outbound 一样,如果用户没有重写channelActive() 方法,那就会调用ChannelInboundHandlerAdapter 的channelActive()方法:

同样地, 在ChannelInboundHandlerAdapter 的channelActive()中,仅仅调用了ctx.fireChannelActive()方法,因此就会进入Context.fireChannelActive() -> Connect.findContextInbound() ->nextContext.invokeChannelActive() ->nextHandler.channelActive() -> nextContext.fireChannelActive()这样的循环中。同理,tail 本身既实现了ChannelInboundHandler 接口,又实现了ChannelHandlerContext 接口,因此当channelActive()消息传递到tail 后,会将消息转递到对应的ChannelHandler 中处理,而tail 的handler()返回的就是tail 本身:

TailContext 的channelActive()方法是空的。如果大家自行查看TailContext 的Inbound 处理方法时就会发现,它们的实现都是空的。可见,如果是Inbound,当用户没有实现自定义的处理器时,那么默认是不处理的。下图描述了Inbound事件的传输过程:

事件总结 

Outbound 事件总结

1、Outbound 事件是请求事件(由connect()发起一个请求,并最终由unsafe 处理这个请求)。

2、Outbound 事件的发起者是Channel。

3、Outbound 事件的处理者是unsafe。

4、Outbound 事件在Pipeline 中的传输方向是tail -> head,这里指ContextHandler

5、在ChannelHandler 中处理事件时,如果这个Handler 不是最后一个Handler,则需要调用ctx 的方法(如: ctx.connect()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。

6、Outbound 事件流:Context.OUT_EVT() -> Connect.findContextOutbound() -> nextContext.invokeOUT_EVT()-> nextHandler.OUT_EVT() -> nextContext.OUT_EVT()

Inbound 事件总结

1、Inbound 事件是通知事件,当某件事情已经就绪后,通知上层。

2、Inbound 事件发起者是unsafe。

3、Inbound 事件的处理者是Channel,如果用户没有实现自定义的处理方法,那么Inbound 事件默认的处理者是 TailContext,并且其处理方法是空实现。

4、Inbound 事件在Pipeline 中传输方向是head -> tail。

5、在ChannelHandler 中处理事件时,如果这个Handler 不是最后一个Handler,则需要调用ctx.fireIN_EVT()事 件(如:ctx.fireChannelActive()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。

6、Outbound 事件流:Context.fireIN_EVT() -> Connect.findContextInbound() -> nextContext.invokeIN_EVT() ->nextHandler.IN_EVT() -> nextContext.fireIN_EVT().outbound 和inbound 事件设计上十分相似,并且Context 与Handler 直接的调用关系也容易混淆,因此我们在阅读这里的源码时,需要特别的注意

ChannelHandlerContext 接口

ChannelHandlerContext 代表了 ChanelHandler 和 ChannelPipeline 之间的关联, 每当有 ChanelHandler 添加到 ChannelPipeline 中, 都会创建 ChannelHandlerContext.

ChannelHandlerContext 的主要功能是管理它所关联的 ChannelPipeline 和同一个 ChannelPipeline 中的其他 ChanelHandler 之间的交互.

ChannelHandlerContext 有很多的方法, 其中一些方法也存在于 Channel 和 ChannelPipeline 上, 但是有一点重要的不同.

如果调用 Channel 和 ChannelPipeline 上的这些方法将沿着 ChannelPipeline 进行传播(从头或尾开始).
而调用位于 ChannelHandlerContext 上的相同方法, 则将从当前所关联的 ChannelHandler 开始, 并且只会传播给位于该 ChannelPipeline 中的下一个能够处理该事件的 ChannelHandler.

这样做可以减少 ChannelHandler 的调用开销.

使用 ChannelHandlerContext

上图为 Channel ChannelPipeline ChannelHandler 以及 ChannelHandlerContext 之间的关系.

参考:

Netty源码篇3-ChannelPipeline和ChannelHandler - 掘金 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值