Netty的ChannelPipeline

本文详细解析了Netty中的ChannelPipeline工作原理,包括双向链表结构、Handler的添加过程及事件传递机制。针对Inbound与Outbound事件的不同处理流程进行了说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

* <pre>
*                                                 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)            |
*  +-------------------------------------------------------------------+
* </pre>

 

以上是Netty作者在ChannelPipeLine的注释给出的图,可见在ChannelPipeline中维护这一个双向链表。在ChannelPipeLine中,具体处理相关通知请求的成员成员称为ChannelHandlerContext,也是pipeline中双向链表的成员。

而在其中的又根据是请求还是通知分为不同的处理模式由图可得当作位通知请求需要去通知上一级的channel的时候,就需要由图中的底向上的通过调用fireIN_EVT()来在双向链表中传递。并由图中的序号可见,在这里的inbound contexthandler传递,并从双线链表的头部开始传递。

而请求的操作则是有挺大的差异虽然都是通过fireIN_EVT()方法在双向链表当中传递,但是由图可知,是从图中自顶网下传递的,而由此可见,请求是从链表的末尾开始传递请求的。处理请求的成员则是outbound contexthandler。

在注释中也给出了相应的例子来理解上面的内容

 

* <pre>
* {
@link ChannelPipeline} p = ...;
* p.addLast("1", new InboundHandlerA());
* p.addLast("2", new InboundHandlerB());
* p.addLast("3", new OutboundHandlerA());
* p.addLast("4", new OutboundHandlerB());
* p.addLast("5", new InboundOutboundHandlerX());
* </pre>
* In the example above, the class whose name starts with {
@code Inbound} means it is an inbound handler.
* The class whose name starts with {
@code Outbound} means it is a outbound handler.
* <p>
* In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound.
* When an event goes outbound, the order is 5, 4, 3, 2, 1.  On top of this principle, {
@link ChannelPipeline} skips
* the evaluation of certain handlers to shorten the stack depth:
* <ul>
* <li>3 and 4 don't implement {
@link ChannelInboundHandler}, and therefore the actual evaluation order of an inbound
*     event will be: 1, 2, and 5.</li>
* <li>1 and 2 don't implement {
@link ChannelOutboundHandler}, and therefore the actual evaluation order of a
*     outbound event will be: 5, 4, and 3.</li>
* <li>If 5 implements both {
@link ChannelInboundHandler} and {@link ChannelOutboundHandler}, the evaluation order of
*     an inbound and a outbound event could be 125 and 543 respectively.</li>
* </ul>

以上的给出的例子构造了一个含有顺序五个context handler成员的pipeline,而其中1和2分别为InBoundHandler,3和4分别为OutBoundHandler,5则既为InBoundHandler也为OutBoundHandler。当这样的一个pipeline在在处理inbound event的时候,处理的顺序则为1,2,5.而在处理outbound event的时候,处理得当顺序则为5,4,3.可以在这里看到pipeline对于不同请求通知的调用形式。

 

每一个Channel都对应着一个ChannelPipeline,并且每次在Channel的创建时,ChannelPipeline都会被自动创建出来。

可以看到,DefaultChannelPipeline的构造方法。

public DefaultChannelPipeline(AbstractChannel channel) {
    if (channel == null) {
        throw new NullPointerException("channel");
    }
    this.channel = channel;

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

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

 

在构造方法的一开始就创建了一个tailContext和headContext作为双向链表的头部和尾部。也就是在这里就已经完成创建了双向链表。但是,其实在接下来不管怎么调用addFirst或者addLast,双向链表的头部和尾部都是以上这两者。

 

可以看到addFirst()方法,也就是往双向链表顶部添加contextHandler的方法。

 

public ChannelPipeline addFirst(EventExecutorGroup group, final String name, ChannelHandler handler) {
    synchronized (this) {
        checkDuplicateName(name);
        AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
        addFirst0(name, newCtx);
    }

    return this;
}

 

首先,会对添加进来进来的handler的name进行判断,是否已经添加过,通过checkDuplicateName()方法来进行判断。

 

private void checkDuplicateName(String name) {
    if (name2ctx.containsKey(name)) {
        throw new IllegalArgumentException("Duplicate handler name: " + name);
    }
}

 

pipeline中,存在一个名为name2ctx的map来为添加进来的handler进行去重,保证没有重复名字的handler添加进handler。

 

之后则会将准备添加进来的handler生成包装类AbstractChannelHandlerContext。

生成包装类的过程中主要还是为了设置该handler的inbound还是outbound属性。

DefaultChannelHandlerContext(
        DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) {
    super(pipeline, group, name, isInbound(handler), isOutbound(handler));
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    this.handler = handler;
}
private static boolean isInbound(ChannelHandler handler) {
    return handler instanceof ChannelInboundHandler;
}

private static boolean isOutbound(ChannelHandler handler) {
    return handler instanceof ChannelOutboundHandler;
}

 

可以看到,这里根据是否继承的是Outbound还是Inbound进行了判断并且作为属性作为了AbstractChannelPipeline的构造方法的成员用来设置inbound和outbound的属性。

 

在完成了包装类的创建之后进入addFirst0()继续添加handler。

private void addFirst0(String name, AbstractChannelHandlerContext newCtx) {
    checkMultiplicity(newCtx);

    AbstractChannelHandlerContext nextCtx = head.next;
    newCtx.prev = head;
    newCtx.next = nextCtx;
    head.next = newCtx;
    nextCtx.prev = newCtx;

    name2ctx.put(name, newCtx);

    callHandlerAdded(newCtx);
}

 

首先会通过checkMultiplicity()方法,来防止同一个handler包装类的重复添加。

 

private static void checkMultiplicity(ChannelHandlerContext ctx) {
    ChannelHandler handler = ctx.handler();
    if (handler instanceof ChannelHandlerAdapter) {
        ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
        if (!h.isSharable() && h.added) {
            throw new ChannelPipelineException(
                    h.getClass().getName() +
                    " is not a @Sharable handler, so can't be added or removed multiple times.");
        }
        h.added = true;
    }
}

 

在判断未添加过之后,会给added属性设为true,防止在后面重复往pipeline添加同一个handler。

 

之后就是简单的双向链表操作以保证head仍旧在双向链表的顶端。

以上就是往pipeline中双向链表头部添加handler的例子。

接下来可以看到pipeline中双向链表的事件传递。

当一个Channel完成了端口与selector之间的注册的时候,会调用pipeline的fireChannelRegistered()方法,通知通道已经注册完成的通知的目的。

public ChannelPipeline fireChannelRegistered() {
    head.fireChannelRegistered();
    return this;
}

可以见到,是从双向链表的头开始的,也就是headContext开始向链表后面的handler成员开始,那么会直接调用head的fireChannelRegistered()方法。这一方法实现在AbstractChannelHandlerContext中,也就是说,基本所有的handler都会实现这一方法。

public ChannelHandlerContext fireChannelRegistered() {
    final AbstractChannelHandlerContext next = findContextInbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRegistered();
    } else {
        executor.execute(new OneTimeTask() {
            @Override
            public void run() {
                next.invokeChannelRegistered();
            }
        });
    }
    return this;
}

首先,会通过findContextInbound()方法寻找下一个用来调用的handler。

private AbstractChannelHandlerContext findContextInbound() {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

 

可以看到从链表中寻找下一个作为inbound属性为true的handler,这也印证了刚才注释所解释的内容。从双向链表头部开始调用的事件通知是有inbound属性为true的handler来参与相应的实现的。

 

在找到了相应的inbound属性为true的handler,则会通过invokeChannelRegistered(0方法来调用这个handler的channelRegistered()方法。

private void invokeChannelRegistered() {
    try {
        ((ChannelInboundHandler) handler()).channelRegistered(this);
    } catch (Throwable t) {
        notifyHandlerException(t);
    }
}

 

inboud为true属性的handler首先要继承了ChannelInboundHandlerAdapter类,如果没有特地重写了channelRegistered()方法,则会直接实现该类的方法。

 

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelRegistered();
}

 

很简单的方法,默认并没有任何实现,而是直接与head的handler一样调用双向链表下一个handler的channelRegistered()方法,这样,也保证完成了双向链表中的事件传递。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值