Netty5源码分析(四) -- 事件分发模型

13 篇文章 3 订阅

所谓Netty5的事件分发模型,主要指的是ChannelPipline, ChannelHandlerContext, ChannelHandlerInvoker, ChannelHandler, Unsafe 这几个核心接口之间的交互模型。


首先是ChannelPipline接口,它是Netty中事件分发的路径。每个Channel都会绑定一个ChannelPipeline来分发事件。

public interface Channel extends AttributeMap, Comparable<Channel> {
ChannelPipeline pipeline();
}

ChannelPipeline是一个大的接口,是一个Facade门面模式的实例。它主要包括三部分的接口,

1. 链表的接口,包括各种遍历,修改链表的操作

2. inbound事件的接口,以fireXXXX开头的方法

3. outbound事件的接口, 不带fire的方法,比如read, write,bind, connect等

inbound,outbound事件是Netty抽象的事件概念,从底层IO事件到用户事件的方向是inbound事件,从用户事件到底层IO事件的方向是outbound事件


DefaultChannelPipeline是ChannelPipeline接口的具体实现,它处理实际的事件分发。它采用了两个单链表head, tail 来处理inbound,outbound事件。

单链表的节点是ChannelHandlerContext,它通过next, prev两个指针指向前后的节点。

head链表的第一个节点是HeadHandler, tail节点的第一个节点是TailHandler。 HeadHandler里面封装了Unsafe接口, 来进行实际的IO读写。inbound事件从底层IO开始,outbound事件到底层IO结束,所以inbound事件链的起点从HeadHandler开始,outbound事件链的终点在HeadHandler结束

在上一篇将Netty如何注册OP_ACCEPT,OP_READ事件时 http://blog.csdn.net/iter_zc/article/details/39396169,我们看到每次Channel注册到Selector时,只设置了Attachment,没有真正的注册,真正的注册是在注册后调用Channel.read,通过HeadHandler的read方法,调用到AbstractChannel的doBeginRead来把OP_ACCEPT,OP_READ设置到SelectionKey的interestOps中

final class DefaultChannelPipeline implements ChannelPipeline {

    final DefaultChannelHandlerContext head;
    final DefaultChannelHandlerContext tail;

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

        TailHandler tailHandler = new TailHandler();
        tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler);

        HeadHandler headHandler = new HeadHandler(channel.unsafe());
        head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);

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

}
final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext { 

   volatile DefaultChannelHandlerContext next;
    volatile DefaultChannelHandlerContext prev;

    private final AbstractChannel channel;
    private final DefaultChannelPipeline pipeline;
    private final String name;
    private final ChannelHandler handler;
    private boolean removed;

    final int skipFlags;

    // Will be set to null if no child executor should be used, otherwise it will be set to the
    // child executor.
    final ChannelHandlerInvoker invoker;
    private ChannelFuture succeededFuture;
}

static final class HeadHandler extends ChannelHandlerAdapter {

        protected final Unsafe unsafe;

        protected HeadHandler(Unsafe unsafe) {
            this.unsafe = unsafe;
        }

}


ChannelHandlerContext,它相当于链表中的节点,从上面的代码中可以看到,它关联了一个ChannelHandler和ChannelHandlerInvoker。

ChannelHandlerInvoker封装了线程池调用ChannelHandler的接口。DefaultChannelHandlerInvoker是默认实现,它关联了一个EventExecutor来实际执行ChannelHandler,这里实际的对象是NioEventLoop,使用单线程来执行一个Channel所有的ChannelHandler。

DefaultChannelHandlerInvoker使用了ChannelHandlerInvokerUtil来调用ChannelHandler的方法。这里我们看到ChannelHandlerInvoker的模型

1. DefaultChannelHandlerInvoker负责选择执行的线程

2. ChannelHandlerInvokerUtil调用ChannelHandler的方法

public class DefaultChannelHandlerInvoker implements ChannelHandlerInvoker {

    private final EventExecutor executor;

    @Override
    public void invokeChannelRegistered(final ChannelHandlerContext ctx) {
        if (executor.inEventLoop()) {
            invokeChannelRegisteredNow(ctx);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeChannelRegisteredNow(ctx);
                }
            });
        }
    }

public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {

    private final ChannelHandlerInvoker invoker = new DefaultChannelHandlerInvoker(this);
}

public final class ChannelHandlerInvokerUtil {

    public static void invokeChannelRegisteredNow(ChannelHandlerContext ctx) {
        try {
            ctx.handler().channelRegistered(ctx);
        } catch (Throwable t) {
            notifyHandlerException(ctx, t);
        }
    }
}

DefaultChannelHandlerContext还保存了一个它关联的ChannelHandler的skipflag信息。skipflag指的是一个ChannelHandler可以使用@Skip注解来表示它不处理某个方法。DefaultChannelHandlerContext记录了这些信息,可以在事件到来的时候直接判断是否要跳过处理。


private static int skipFlags0(Class<? extends ChannelHandler> handlerType) {
        int flags = 0;
        try {
            if (handlerType.getMethod(
                    "handlerAdded", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
                flags |= MASK_HANDLER_ADDED;
            }
            if (handlerType.getMethod(
                    "handlerRemoved", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
                flags |= MASK_HANDLER_REMOVED;
            }
            if (handlerType.getMethod(
                    "exceptionCaught", ChannelHandlerContext.class, Throwable.class).isAnnotationPresent(Skip.class)) {
                flags |= MASK_EXCEPTION_CAUGHT;
            }
            if (handlerType.getMethod(
                    "channelRegistered", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
                flags |= MASK_CHANNEL_REGISTERED;
            }
}


ChannelHandler接口定义了如何处理inbound和outbond事件的方法,是职责链节点的具体实现,也是业务代码编写地方。Netty抽象了Pipeline这个职责链,将底层IO处理和业务处理进行了隔离,使用户可以关注与业务逻辑的实现。


public interface ChannelHandler {

    ///
    // Inbound event handler methods //
    ///

    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

  
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    // Outbound event handler methods //
    

    
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

   
    void connect(
            ChannelHandlerContext ctx,
            SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception;
}

Unsafe定义了实际IO处理的接口,它的含义不是说它的方法是不安全的,而是说它的接口是给框架本身调用的,不要暴露给业务层调用。

Unsafe的最底层实现类采用了模板方法模式,NioMessageUnsafe绑定到了NioServerSocketChannel,NioByteUnsafe绑定到NioSocketChannel,

最终的IO读写方法实现在NioServerSocketChannel和NioByteUnsafe中,调用了Java的ServerSocketChannel和SocketChannel来实现。

interface Unsafe {

        ChannelHandlerInvoker invoker();

     SocketAddress localAddress();

        SocketAddress remoteAddress();

        void register(ChannelPromise promise);
    
        void bind(SocketAddress localAddress, ChannelPromise promise);
  
        void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
 
        void disconnect(ChannelPromise promise);

        void close(ChannelPromise promise);

        void closeForcibly();
     
        void beginRead();

        void write(Object msg, ChannelPromise promise);

        void flush();
        
        ChannelPromise voidPromise();

        ChannelOutboundBuffer outboundBuffer();
    }

<pre name="code" class="java">
private final class NioByteUnsafe extends AbstractNioUnsafe {
        private RecvByteBufAllocator.Handle allocHandle;

        @Override
        public void read() {
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final int maxMessagesPerRead = config.getMaxMessagesPerRead();
            RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
            if (allocHandle == null) {
                this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
            }
            if (!config.isAutoRead()) {
                removeReadOp();
            }

            ByteBuf byteBuf = null;
            int messages = 0;
            boolean close = false;
            try {
                int byteBufCapacity = allocHandle.guess();
                int totalReadAmount = 0;
                do {
                    byteBuf = allocator.ioBuffer(byteBufCapacity);
                    int writable = byteBuf.writableBytes();
                    int localReadAmount = doReadBytes(byteBuf);
                    if (localReadAmount <= 0) {
                        // not was read release the buffer
                        byteBuf.release();
                        close = localReadAmount < 0;
                        break;
                    }

                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;

                    if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
                        // Avoid overflow.
                        totalReadAmount = Integer.MAX_VALUE;
                        break;
                    }

                    totalReadAmount += localReadAmount;
                    if (localReadAmount < writable) {
                        // Read less than what the buffer can hold,
                        // which might mean we drained the recv buffer completely.
                        break;
                    }
                } while (++ messages < maxMessagesPerRead);

                pipeline.fireChannelReadComplete();
                allocHandle.record(totalReadAmount);

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

//NioSocketChannel.doReadBytes()调用SocketChannel来读取数据
 protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes());
    }

 





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、付费专栏及课程。

余额充值