Netty学习——ChannelPipeLine ChannelHandler ChannelHandlerContext

本文深入解析Netty框架中的Pipeline事件传递机制,探讨ChannelPipeline、ChannelHandler与ChannelHandlerContext三者间的关系,以及如何通过手动调用上下文方法实现事件在不同处理器间的传递。

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

先上官方图

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

这里有一点注意的是往哪是IN,往哪是OUT,

往程序中入的IN 即socket —> 程序 从程序往外走的是OUT 即 程序 —> socket

上图体现了ChannelPipeLine ChannelHandler ChannelHandlerContext 三者的关系,

pipeline是他们的整体,是一条执行链
handler 是用户定义的,处理响应信息和请求信息的的处理器,其中包含的是具体的执行逻辑
context 是沟通handler和pipeline的连接器,执行链的事件向下传递,需要context链表向下next,事件的处理需要context来调用handler的执行逻辑

这里最需要搞明白的就是,事件是怎么沿着执行链向下传递,依次调用各个handler的执行逻辑的

本来我以为pipeline在收到请求后就会从头context开始,一个一个context去next去调用handler的方法,其传递的过程会在context中写好了,但其实并不是。
以inbound的read事件为例,你往pipeline中addLast多个handler,都重写了channelRead方法,运行时,read事件触发后并不会自动经过所有的handler的channelRead方法,需要我们在上一个channelRead中手动将事件传递给下一个cxt,这个read事件才可以触发多个handler的channelRead方法。
也就是说,如果我们不在handler中做合适的处理,向下next context 那么pipeline就无法向下传递事件,那么下面的handler就起不到作用了。

以代码为例,这里我们自定义了两个handler都add到pipeline中

b.group(bossGroup, workerGroup)
                    //5:需要指定使用NioServerSocketChannel这种类型的通道
                    .channel(NioServerSocketChannel.class)//(3) 服务端 -->NioServerSocketChannel
                    //6:一定要使用childHandler 去绑定具体的事件处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() //(4)   childHandler
                    {
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception
                        {
                            sc.pipeline().addLast(new ServerHandler2());
                            sc.pipeline().addLast(new ServerHandler1());//handler中实现真正的业务逻辑
                        }
                    })

handler1

public class ServerHandler1  extends ChannelInboundHandlerAdapter
{

@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
    System.out.println("handler1 complete read");
    ctx.flush();
}

/**
 * 每当从客户端收到新的数据时,这个方法会在收到消息时被调用
 * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
 * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
    try{


        ByteBuf buf = (ByteBuf)msg;
        byte[] data = new byte[buf.readableBytes()];
        buf.readBytes(data);
        String request = new String(data,"utf-8");
        System.out.println("hanlder1:"+request);

        ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()))
                .addListener(ChannelFutureListener.CLOSE);//添加监听 当服务端向客户端发送完数据后,关闭connect连接
        ctx.close();
    }finally{
        // Discard the received data silently.
        ReferenceCountUtil.release(msg);
    }
}

}

handler2

public class ServerHandler2 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("handler2 is reading");
        //do something
        //接收客户端发送的数据 ByteBuf
        ByteBuf buf = (ByteBuf)msg;
        //创建一个和buf长度一样的空字节数组
        byte[] data = new byte[buf.readableBytes()];
        //将buf中的数据读取到data数组中
        buf.readBytes(data);
        //将data数据包装成string输出
        String request = new String(data,"utf-8");
        System.out.println("hanlder2 :" + request);
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handler2 complete read");

        super.channelReadComplete(ctx);
    }
}

然后打开服务器使用客户端请求,可以看到如下输出,这里我们可以看到,读事件经过了这个两个handler

handler2 is reading
hanlder2 :execute
hanlder1:
handler2 complete read
handler1 complete read

但如果我把handler2中的
//super.channelRead(ctx, msg);

注释掉,再次运行

handler2 is reading
hanlder2 :execute
handler2 complete read

我们就可以发现,handler2后面的handler根本就没有执行

所以handler之间的事件传递,需要开发者手动进行

下面接着EventLoop中read事件触发后processSelectedKey的read处理函数来查看pipeline中事件是如何在handler之间传递的

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();//read事件触发,调用channel的read处理函数
    }



public final void read() {
    final ChannelConfig config = config();
    if (shouldBreakReadReady(config)) {
        clearReadPending();
        return;
    }
    final ChannelPipeline pipeline = pipeline();
    final ByteBufAllocator allocator = config.getAllocator();
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
    allocHandle.reset(config);

    ByteBuf byteBuf = null;
    boolean close = false;
    try {
        do {
            byteBuf = allocHandle.allocate(allocator);
            allocHandle.lastBytesRead(doReadBytes(byteBuf));
            if (allocHandle.lastBytesRead() <= 0) {
                // nothing was read. release the buffer.
                byteBuf.release();
                byteBuf = null;
                close = allocHandle.lastBytesRead() < 0;
                if (close) {
                    // There is nothing left to read as we received an EOF.
                    readPending = false;
                }
                break;
            }

            allocHandle.incMessagesRead(1);
            readPending = false;
            //pipeline开始从head处理
            pipeline.fireChannelRead(byteBuf);
            byteBuf = null;
        } while (allocHandle.continueReading());

        allocHandle.readComplete();
        //read结束后,处理函数
        pipeline.fireChannelReadComplete();

        if (close) {
            closeOnRead(pipeline);
        }
    } catch (Throwable t) {
        handleReadException(pipeline, byteBuf, t, close, allocHandle);
    } 
}

上面channel的read方法中主要就是两个部分:
1、将数据读到bytebuf中,然后传入到pipeline中,pipeline.fireChannelRead(byteBuf),使用 handler对数据进行处理
2、数据完毕后,调用pipeline.fireChannelReadComplete();进行读取完毕处理

先看 pipeline.fireChannelRead(byteBuf)

public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}

到了这里,接下来就是调用handler中的channelRead(this, msg)方法了,到了这里,只执行了一个hanlder中的channelRead方法,然后,就没有了???说好的执行链呢,所以到这我们就可以看出来,context的向后传递不是由系统框架自动实现的,需要我们在自定义的handler中手动向后传递, 下面我们来看看,自定义的handler中如何传递事件的,

从上面的测试例子中可以看到,super.channelRead(ctx, msg) 是实现pipeline中事件传递的关键,所以我们从这个方法看起

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ctx.fireChannelRead(msg);
}

public ChannelHandlerContext fireChannelRead(Object msg) {
    invokeChannelRead(this.findContextInbound(), msg);//关键的find方法
    return this;
}

到这,我们可以看到一个事件传递的重要方法findContextInbound()

private AbstractChannelHandlerContext findContextInbound() {
    AbstractChannelHandlerContext ctx = this;

    do {
        ctx = ctx.next;
    } while(!ctx.inbound);//循环,直到找到下一个inbound的handler

    return ctx;
}

到这终于真相大白了
pipeline中事件传递方式就是,事件触发后,先从head开始,向下找到第一个能处理事件的handler,当handler1处理完后,如果在handler1中不手动将事件向下传递,那么事件就终止在当前handler,其他handler中的处理函数就不会触发。如果每个handler都使用super.fire()方法的话,那么事件会在所有的handler中传递一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值