netty源码系列之-03_ChannelPipeline,channelhanddler,channelcontext

我读源码的思路就是先找其中一个一个的小组件看看如何设计和实现的,感觉了解的差不多了再进行串起来,今天先看看Pipeline,从使用的过程中我就一个印象双向链表,并且创建的时候 默认创建一个head和一个tail,里面存放的都是Channelcontext, ChannelConetext从名字上可以理解为上下文,里面主要包装Handler, 这些就是我读源码之前的一点点理解,接下来看看 我理解的是否有偏差,还有哪些没理解的,今天安排上…

概述 ChannelPipeline , Channelhandler ChannelhandlerContext 三者的关系

服务端accept一个新的Channel之后,需要监听这个Channel上的事件,并且将所发生的事件投递到正确的handler上来处理,Netty使用ChannelHandler组件来处理Channel上发生的事件,处理完成之后再将结果发送出去,而ChannelPipeline就是将多个ChannelHandler组合在一起,形成一个链条,这个链条会拦截Channel上的事件,然后在链条中传播。

ChannelHandlerContext是一个特别重要的组件,首先,每一个新创建的Channel都会分配给一个ChannelPipeline,而ChannelHandlerContext将ChannelHandler和ChannelPipeline联系起来,ChannelHandlerContext提供了和ChannelPipeline类似的方法,但是调用ChannelHandlerContext上的方法只会从当前ChannelHandler开始传播,并且只会传播到下一个ChannelHandler上,而调用ChannelPipeline上的方法会沿着链条一直传递下去。

ChannelHandler分为ChannelInboundHandler和ChannelOutboundHandler,分别代表入站处理器和出站处理器,在实际应用中,我们没必要直接实现ChannelInboundHandler接口或者ChannelOutboundHandler接口,下面的图片展示了Netty为我们提供的一些实现子类:

在这里插入图片描述
对于入站处理,我们只需要继承ChannelInboundHandlerAdapter就可以了,然后重写我们需要的方法,对于出站处理来说,只需要继承ChannelOutboundHandlerAdapter子类,然后重写需要的接口就可以了。如果我们不再希望将事件传递下去,你应该在处理完消息之后释放它,否则这个消息会一直传递下去直到最后一个处理器。

三者的关系

在这里插入图片描述

  1. 每档ServerSocket 创建一个新的连接,就会创建一个socket,对应的就是目标客户端
  2. 每一个新创建的socket都将会分配一个全新的ChannelPipeline,(bind方法中 channel 的initandregist方法中完成)
  3. 每一个ChannelPipeline内部都含有多个ChannelHandlerContext

tips:
从上图可以看出

  1. ChannelSocket和channelPipeline 是一对一的关联关系,而pipeline内部的多个context形成了链表,context只是对handler的封装
  2. 当一个请求进来的时候,会进入socket对应的pipeline,并经过pipeline所有的handler,这就是设计模式中的过滤器模式和责任链模式

先看看类结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
低版本while 条件直接写while(!ctx.inbound) while(!oubbound)
在这里插入图片描述

在这里插入图片描述

ChannelInitializer 也是一种handler

 public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)  //通过注入channelclass 此时初始化化一个channle工厂
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>(){
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("客户端socketchannel"+ socketChannel.hashCode());
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println(".....服务器 is ready...");
            ChannelFuture cf = serverBootstrap.bind(8888).sync();
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 8888 成功");
                    } else {
                        System.out.println("监听端口 8888 失败");
                    }
                }
            });
            //对关闭通道事件  进行监听
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }



    }

注意childHandler这个方法,这个方法里面传递了一个ChannelInitializer类型的对象,ChannelInitializer继承了ChannelInboundHandlerAdapter类,所以它是一个入站处理器,当这个参数调用它的initChannel方法的时候就会去初始化ChannelPipeline,什么时候会调用initChannel这个方法呢?

下面是我追踪的方法调用链条:


   -> ChannelInitializer.initChannel
   -> ChannelInitializer.channelRegistered
   -> AbstractChannelHandlerContext.invokeChannelRegistered
   -> AbstractChannelHandlerContext.fireChannelRegistered
   -> AbstractChannel.AbstractUnsafe.register0
   -> AbstractChannel.AbstractUnsafe.register
   -> AbstractBootstrap.initAndRegister
   -> AbstractBootstrap.doBind
   -> AbstractBootstrap.bind

上面的方法调用时倒过来的,最开始的方法是AbstractBootstrap.bind方法,这个已经分析过了,其实将这个调用链路和EventLoop的初始化结合起来,就可以理解ChannelInitializer的initChannel方法是怎么被执行的了。也就可以理解ChannelPipeline是什么时候被初始化的了,知道了是什么时候被初始化的,那下面来分析一下
ChannelPipeline是怎么初始化的,以及ChannelPipeline的工作方式是怎么样的。其实第一个问题很简单,因为我们上文已经分析了initChannel被执行的流程,并且已经知道ChannelPipeline是在initChannel方法中被初始化的,那初始化过程的分析只需要沿着initChannel方法往下追踪就可以发现了,下面来看一下这个方法被执行的上下文:

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
                //这里初始化调用用户写的initChannel方法
            } 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 {
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }

可以发现,在调用了initChannel之后,有一个remove方法被调用了,参数是ctx。下面来分析一下remove方法到底做了什么:

@Override
    public final ChannelHandlerContext context(ChannelHandler handler) {
        ObjectUtil.checkNotNull(handler, "handler");

        AbstractChannelHandlerContext ctx = head.next;
        for (;;) {

            if (ctx == null) {
                return null;
            }

            if (ctx.handler() == handler) {
                return ctx;
            }

            ctx = ctx.next;
        }
    }
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
        assert ctx != head && ctx != tail;

        synchronized (this) {
            atomicRemoveFromHandlerList(ctx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we remove the context from the pipeline and add a task that will call
            // ChannelHandler.handlerRemoved(...) once the channel is registered.
            if (!registered) {
                callHandlerCallbackLater(ctx, false);
                return ctx;
            }

            EventExecutor executor = ctx.executor();
            if (!executor.inEventLoop()) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        callHandlerRemoved0(ctx);
        return ctx;
    }

结合两个方法,我们可以得出结论,在执行了initChannel方法之后,Pipeline会将这个this从Pipeline中删除掉,而this是什么?就是我们当初为服务端配置的那个ChannelInitializer,ChannelInitializer是一种特殊的ChannelHandler,它可以作为Pipeline初始化的handler,初始化完成之后它会自动被Pipeline删除,仅留下我们配置的ChannelHandler。

至此,我们已经分析了ChannelInitializer的initChannel方法调用的全链路,并且可以知道ChannelInitializer在初始化Pipeline完成之后就会从其中删除,留下用户配置的ChannelHandler,下面来分析一下ChannelPipeline是如何工作的。首先,我们在上面的remove方法中貌似看出了一些猫腻,ChannelPipeline貌似是一种链表,在上文中也提到ChannelPipeline是一种链表,并且它是一种双向链表,对于入站数据来说将从左到右传播数据,而对于出站数据则从右往左传播数据。为了更好的分析它,现在结合实际的例子看一下。在上面给出的例子中,有类似下面的代码:


                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }

在initChannel中使用了Pipeline的addLast方法来将我们的EchoServerHandler添加到Pipeline的尾端,我们来看一下addLast方法的内容:


    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

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

从上面的方法调用来看,ChannelPipeline并不允许你重复插入ChannelHandler到Pipeline中去,并且Pipeline管理的双向链表貌似存储的不是ChannelHandler本身,而是使用newContext方法将其变为了例外一种类型,而这种类型就是我们上文中提到的AbstractChannelHandlerContext,ChannelPipeline管理着的就是一个由多个AbstractChannelHandlerContext节点组成的双向链表,而AbstractChannelHandlerContext内部也是一个双向链表,分别存储着前一个AbstractChannelHandlerContext和后一个AbstractChannelHandlerContext。后面的addLast0就是将新的ChannelHandler保存在双向链表的尾部节点。除了addLast方法,ChannelPipeline还提供了大量操作ChannelPipeline的方法,不仅可以添加ChannelHandler,并且还可以删除ChannelHandler,而且还可以replace,更多的方法参考Netty的Api文档。需要注意的一点是,这些更改是实施动态进行的,这也就预示着我们可以更加灵活的来操作我们的数据,更加动态的对出站和入站的数据进行加工。

ChannelPipeline的事件传播

现在来分析一下ChannelPipeline是如何传播事件的。首先再次说明,ChannelHandler分为出站和入站,不同的事件会走不同的方向。出站类型的handler不会处理入站数据,反过来也是。上文中提到,如果我们想写一个处理入站数据的handler,只需要继承ChannelInboundHandlerAdapter就可以了,而处理出站数据则继承ChannelOutboundHandlerAdapter就可以了。所以我们从这两个类中开始分析,首先分析入站数据在ChannelPipeline中的传播。当有数据可以读的时候,channelRead方法会被触发,下面是它的方法:


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

可以看到,这个方法仅仅是调用了ChannelHandlerContext的fireChannelRead方法,这个方法在AbstractChannelHandlerContext中被重写,下面是它的实现细节:


    public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(), 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() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

可以发现,在fireChannelRead方法中,调用了findContextInbound()方法来寻找下一个符合要求的ChannelHandler,然后调用invokeChannelRead来调用下一个ChannelHandler的invokeChannelRead方法,也就是将时间传递给了下一个ChannelHandler,下面来看一下findContextInbound方法的细节:


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

AbstractChannelHandlerContext对象会有两个标志inbound和outbound,不同的handler实现会设置相应的标志位。findContextInbound方法的含义就是在ChannelPipeline中寻找下一个属于入站属性的handler,可以看到,寻找的方向是从head到tail。下面来分析一下出站数据传播的细节。我们从ChannelOutboundHandlerAdapter的write方法开始分析


    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
       ctx.write(msg, promise);
   }

当然你需要继承ChannelOutboundHandlerAdapter并且重写write方法来实现你的业务处理逻辑,但是你想要传递数据,你就需要调用ctx.write(msg, promise)方法,它会调用AbstractChannelHandlerContext的等效方法,下面是一个它调用链路上的方法:


    private void write(Object msg, boolean flush, ChannelPromise promise) {
       AbstractChannelHandlerContext next = findContextOutbound();
       final Object m = pipeline.touch(msg, next);
       EventExecutor executor = next.executor();
       if (executor.inEventLoop()) {
           if (flush) {
               next.invokeWriteAndFlush(m, promise);
           } else {
               next.invokeWrite(m, promise);
           }
       } else {
           AbstractWriteTask task;
           if (flush) {
               task = WriteAndFlushTask.newInstance(next, m, promise);
           }  else {
               task = WriteTask.newInstance(next, m, promise);
           }
           safeExecute(executor, task, promise, m);
       }
   }

    private AbstractChannelHandlerContext findContextOutbound() {
       AbstractChannelHandlerContext ctx = this;
       do {
           ctx = ctx.prev;
       } while (!ctx.outbound);
       return ctx;
   }

首先通过findContextOutbound方法来寻找下一个符合要求的handler,这个方法将向前寻找,也就是出站事件的传播方向为从后往前。到此我们明白了ChannelPipeline的事件传播方向。对于入站数据,如果传递到了tail节点,会发生什么呢?回忆一下invokeWriteAndFlush方法,它有一个判断invokeHandler()方法,下面是它的细节:

    private boolean invokeHandler() {
       // Store in local variable to reduce volatile reads.
       int handlerState = this.handlerState;
       return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
   }

我们只需要关系handlerState == ADD_COMPLETE的时候会返回true,那么就会执行下面的方法:


           invokeWrite0(msg, promise);
           invokeFlush0();

    private void invokeWrite0(Object msg, ChannelPromise promise) {
       try {
           ((ChannelOutboundHandler) handler()).write(this, msg, promise);
       } catch (Throwable t) {
           notifyOutboundHandlerException(t, promise);
       }
   }

而invokeWrite0方法中会使用一个方法 handler(),它的意思实际上是返回当前context,我们再来看一下两个特殊的context,一个是head节点,一个是tail节点,下面是它们的构造函数:


        HeadContext(DefaultChannelPipeline pipeline) {
           super(pipeline, null, HEAD_NAME, false, true);
           unsafe = pipeline.channel().unsafe();
           setAddComplete();
       }
       
       TailContext(DefaultChannelPipeline pipeline) {
           super(pipeline, null, TAIL_NAME, true, false);
           setAddComplete();
       }        
    final void setAddComplete() {
       for (;;) {
           int oldState = handlerState;
           // Ensure we never update when the handlerState is REMOVE_COMPLETE already.
           // oldState is usually ADD_PENDING but can also be REMOVE_COMPLETE when an EventExecutor is used that is not
           // exposing ordering guarantees.
           if (oldState == REMOVE_COMPLETE || HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
               return;
           }
       }
   }

他们都执行了一个方法setAddComplete,而这个方法的作用就是将状态设置为REMOVE_COMPLETE。也就是说,对于入站事件,要是事件传播到了tail,那么就会执行TailContext的等效方法,比如read,就会执行TailContext的channelRead方法:


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

    protected void onUnhandledInboundMessage(Object msg) {
       try {
           logger.debug(
                   "Discarded inbound message {} that reached at the tail of the pipeline. " +
                           "Please check your pipeline configuration.", msg);
       } finally {
           ReferenceCountUtil.release(msg);
       }
   }

可以看到消息传递到tail之后,就不会再传递下去了,并且会释放它。出站事件也是类似,当事件传递到head的时候,会调用HeadContext的等效方法,然后就不会再传递下去了。

在这里插入图片描述
在这里插入图片描述
此时根据newChannelPipeline() 方法跟进就可以,比较简单不做过多的说明
在这里插入图片描述

FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值