Java-IO系列:Netty中DefaultChannelPipeline

类图

在这里插入图片描述DefaultChannelPipeline是一个Channel中用于处理各种入,出事件的流水线。
DefaultChannelPipeline内部通过双向链表维护了一个pipeline中可以处理事件的处理器列表。每个处理器都是一个AbstractChannelHandlerContext的子类。AbstractChannelHandlerContext中有两个链表相关的属性,prev和next,分别代表该节点的前后相邻节点。
而AbstractChannelHandlerContext有三个子类。分别是HeadContext,TailContext,DefaultChannelHandlerContext。

1、HeadContext,就是链表的表头。
HeadContext持有一个unsafe对象,unsafe对象用于处理各种入管道事件,比如从socket读取内容等。
2、TailContext,链表的表尾
3、DefaultChannelHandlerContext。链表除了两端节点外的中间节点都是DefaultChannelHandlerContext;该类有一个重要的属性是handler。也就是用户自定义的handler。

也就是说用户通过定义handler,之后添加handler到pipeline就可以增加自定义的数据处理逻辑啦。

示意图

在这里插入图片描述上图是DefaultChannelPipeline中HandlerContext的链表关系示意,而每个handler能处理的能力,是handler决定的,实现ChannelInboundHandler和ChannelOutboundHandler接口,代表可以处理channel的in和out事件。按需实现即可。

read场景

NioEventLoop的SelectKey有可读事件的时候。通过unsafe.read()去读取数据。

NioEventLoop.java

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        
  			...
  			
            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
       ...
    }

unsafe是一个NioSocketChannelUnsafe对象。最终会调用pipeline的读数据方法

        @Override
        public final void read() {
			...
            final ChannelPipeline pipeline = pipeline();
			...

            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.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
				...
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
         ...
            }
        }
    }

这段核心代码,主要的功能,就是申请一个长度为1024的ByteBuf。从channel中读取字节,存储到ByteBuf中。之后调用channel的pipeline对象对byteBuf进行读取。
代码如下:

DefaultChannelPipeline.java

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

可以看到fireChannelRead方法,代理了一下AbstractChannelHandlerContext.invokeChannelRead(head, msg);方法,第一个参数是head。也就是pipeline中的链表表头,HeadContext对象。

进一步看invokeChannelRead方法。

AbstractChannelHandlerContext.java

    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() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

EventExecutor executor = next.executor(); 这句是从HeadContext拿到所属Channel,进一步从Channel中获取该Channel所属的NioEventLoop。

		if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }

这一坨代码,源码中常见的套路。也就是先检查NioEventLoop的任务已经运行了,如果运行了,则直接调用HeadContext的inivokeChannelRead方法。
如果没有运行,则启动线程把NioEventLoop的任务跑起来。并且将 HeadContext的inivokeChannelRead作为Task添加到NioEventLoop的任务队列里面。异步运行。

重点看下invokeChannelRead方法

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

((ChannelInboundHandler) handler()).channelRead(this, msg);这句就是调用HeadContext的ChannelRead方法。之所以能强制转换成ChannelInBoundHandler,是因为HeadContext实现了InBound和OutBound接口。

HeadContext.java 中实现的ChannelInBoundHandler的channelRead方法。

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ctx.fireChannelRead(msg);
        }

ctx.fireChannelRead,因为HeadContext继承了AbstractChannelHandlerContext方法。fireChannelRead方法,是AbstractChannelHandlerContext的方法实现。看源码如下:

AbstractChannelHandlerContext.java

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
        return this;
    }

这个函数分解来看。
findContextInbound(MASK_CHANNEL_READ)方法实现如下:

    private AbstractChannelHandlerContext findContextInbound(int mask) {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.next;
        } while ((ctx.executionMask & mask) == 0);
        return ctx;
    }

也就是从HeadContext开始逐步往链表尾部方向遍历,找到InBound的AbstractChannelHandlerContext。然后通过再调用invokeChannelRead。

总结一下

当监听的Socket有可read的数据时,从Pipeline的HeadContext开始,递归找到下一个具备InBoundHandler的AbstractChannelHandlerContext。调用其handler的channelRead方法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值