从Netty源码看心跳超时机制

本文深入探讨Netty框架的心跳机制,详细分析了IdleStateHandler如何实现读写空闲超时处理,包括其内部延时任务的调度逻辑,以及如何通过触发userEventTriggered方法来响应心跳超时。

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


类似于Mina的sessionIdle,当Netty中的读写空闲(超时)后,会调用 userEventTriggered方法,覆写这个方法就可以对心跳超时进行一定的逻辑处理。
比如这样:

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
			switch (event.state()) {
				case WRITER_IDLE:
                    L.d("------write idle-------");
                    ctx.writeAndFlush(heartBeat);
					break;
				case READER_IDLE:
                    L.d("------read idle-------");
					break;
				case ALL_IDLE:
                    L.d("------all idle-------");
					break;
			}
		}
	}

还是得概览整个连接流程,才能准确定位这个方法触发的时机。

channel的建立

一般来讲,作为客户端连接时,代码是这样写的:

    @Test
    public void testFlow(){
        EventLoopGroup group = new NioEventLoopGroup();

		try {
            Bootstrap bootstrap = new Bootstrap()
					.group(group)
					.channel(NioSocketChannel.class)
					.handler(new ChannelInitializer<Channel>() {
						@Override
						protected void initChannel(Channel ch) {
//                          ch.pipeline().addLast(new IdleStateHandler(10, 10, 10));
						}
					});

            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8000);
		}finally {
            group.shutdownGracefully();
		}
    }

还是从connect连接开始,Bootstrap的连接方法是这样的:

//Bootstrap.java
    public ChannelFuture connect(String inetHost, int inetPort) {
        return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }

    public ChannelFuture connect(SocketAddress remoteAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

最终的逻辑都会到doResolveAndConnect方法里来,而这里initAndRegister方法在父类AbstractBootstrap中实现。
initAndRegister方法主要作用有三个:

  1. 创建channel
  2. 初始化channel
  3. 注册channel(将channel加入线程中)
//AbstractBootstrap.java
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

可以看到channel是由channelFactory新建的,而这个channelFactory是在调用Bootstrap.channel(…)的时候传入的,他的父类AbstractBootstrap实现了这个方法:

//AbstractBootstrap.java
    public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(
                ObjectUtil.checkNotNull(channelClass, "channelClass")
        ));
    }

ReflectiveChannelFactory这个类很直接,就是利用反射创建实例,传入什么类,返回一个这个类的新的实例,传入的是NioSocketChannel.class,这里newChannel自然也是返回的NioSocketChannel。
这个类的代码量较少:

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Constructor<? extends T> constructor;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) + " does not have a public non-arg constructor", e);
        }
    }

    @Override
    public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }

    @Override
    public String toString() {
        return StringUtil.simpleClassName(ReflectiveChannelFactory.class) +
                '(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)";
    }
}

创建了channel,接下来是初始化,初始化的方法是在Bootstrap中实现的:

//Bootstrap.java
    void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        p.addLast(config.handler());

        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }

        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }
        }
    }

主要作用是添加Handler和配置Option以及初始化Attribute。

channel的注册

channel被创建后并初始化后需要做的事情就是注册channel,其实就是将channel放入线程中。这一段逻辑把各种线程相关的类理顺即可。最开始注册只有这一行代码:

        ChannelFuture regFuture = config().group().register(channel);

但这一行代码隐藏了一些细节,我们知道config().group()其实就是传入的NioEventLoopGroup,准确来说,这是一个线程池,这个类继承关系是这样的:
NioEventLoopGroup继承关系
当我们使用

         EventLoopGroup group = new NioEventLoopGroup();

创建线程池的时候,最终会调用到MultithreadEventExecutorGroup中的构造方法:

//MultithreadEventExecutorGroup.java
    private final EventExecutor[] children;
	
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

只需要关注这个childrenchooser就行了,children的真正创建赋值是使用newChildren方法,这个方法又回到NioEventLoopGroup类中进行了实现:

//NioEventLoopGroup.java
    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

NioEventLoop这个类也有自己的继承关系:
NioEventLoop
而chooser是通过newChooser赋值,并把之前创建好的children放进去,newChooser方法是在DefaultEventExecutorChooserFactory实现的:

//DefaultEventExecutorChooserFactory.java
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

这里的isPowerOfTwo方法是通过位与运算判断线程池的长度是否为2的冪,比如1,2,4,8等;

回到

        ChannelFuture regFuture = config().group().register(channel);

这里,config().group()是NioEventLoopGroup,register方法在其父类MultithreadEventLoopGroup中实现了:

//MultithreadEventLoopGroup.java
    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

这里的next()是父类实现的:

//MultithreadEventExecutorGroup.java
    @Override
    public EventExecutor next() {
        return chooser.next();
    }

最终是为了选择线程池:

//PowerOfTwoEventExecutorChooser.java
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
//GenericEventExecutorChooser.java
        @Override
        public EventExecutor next() {
            return executors[Math.abs(idx.getAndIncrement() % executors.length)];
        }        

值得一提的是,我们通常会发现线程名字为"nioEventLoopGroup-2-1”的字样,“2"代表着第2个线程池,"1”代表着这个线程池中的第1条线程;这是因为第1个线程池是一个全局的线程池GlobalEventExecutor,用于监听和终止某个channel
而在前面已经知道executors其实就是MultithreadEventExecutorGroup类中的children,也就是NioEventLoop,所以register方法最终还是要落在NioEventLoop上,此方法在其父类SingleThreadEventLoop中实现:

//SingleThreadEventLoop.java
    @Override
    public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }

    @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

所以register最终被unsafe()代理了。

这个unsafe()是谁呢?这里我们已经知道channel其实就是新建的NioSocketChannel,顺藤摸瓜就行了。
这里先看一下NioSocketChannel的继承关系:
NioSocketChannel
unsafe()方法在其父类AbstractNioChannel中有实现:

    @Override
    public NioUnsafe unsafe() {
        return (NioUnsafe) super.unsafe();
    }

再往上:

//AbstractChannel.java
    @Override
    public Unsafe unsafe() {
        return unsafe;
    }

结果是个成员变量,这个成员变量是在构造方法中新建的:

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

    protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }

这里顺便看到pipeline是新建的默认DefaultChannelPipeline;
然后newUnsafe却是在子类NioSocketChannel中实现:

//NioSocketChannel.java
    @Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();
    }

我们又得看下这个类的继承关系了:
NioSocketChannelUnsafe

所有Unsafe类都是作为内部类在Channel类中实现的。
回到这一句

        promise.channel().unsafe().register(this, promise);

最终在AbstractChannel类的内部类AbstractUnsafe中实现:

        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {//首次一般进入这里
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

        private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();

                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

这一段的逻辑就是判断channel是否已经运行在当前线程中,如果不是当前线程就启用线程运行register0方法,当然首次一般是主线程进行操作,所以eventLoop.inEventLoop()判断往往为false,自然而然就会使用eventLoop.execute把channel以及后续的操作全部放在了这个新建的线程eventLoop中了;
register0方法则开始调用整个handler链上的fireChannelRegistered,如果连接已经打开且是首次打开,那么紧接着就调用fireChannelActive方法,否则就开始读取数据。

Handler的调用

如此一来,上文注册流程中看到的

pipeline.fireChannelRegistered()

就会顺势调用;添加的Handler就会开始被调用,这就到了真正可见的地方了。

//DefaultChannelPipeline.java
    @Override
    public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(head);
        return this;
    }

    @Override
    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

转看AbstractChannelHandlerContext类中的方法:

//AbstractChannelHandlerContext.java
    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

    static void invokeChannelActive(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
    }

next是自身,于是这种静态方法又调用自身invokeChannelXXX的方法:

//AbstractChannelHandlerContext.java
    private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

    private void invokeChannelActive() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelActive(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelActive();
        }
    }

这里的handler()是每个handler被封装为HandlerContext都要实现的,其实就是封装的本身。当我们使用pipeline.addXXX的时候,都是这样添加的:

//DefaultChannelPipeline.java
    @Override
    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 (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

然后可以看到,传入的handler都会被newContext方法封装为一个handlerContext:

//DefaultChannelPipeline.java
    private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
    }

而这个DefaultChannelHandlerContext 类很简单,就是传入了handler,handler()方法也就是返回传入的handler:

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {

    private final ChannelHandler handler;

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, handler.getClass());
        this.handler = handler;
    }

    @Override
    public ChannelHandler handler() {
        return handler;
    }
}

这里注意最开始调用的时候:

AbstractChannelHandlerContext.invokeChannelRegistered(head);

里面这个head,这个head是DefaultChannelPipeline创建时就创建的默认的一个AbstractChannelHandlerContext。
handler集合本就是一个链表结构,而这个head就是默认的链表的头。
看DefaultChannelPipeline的构造方法,分别新建了头与尾两个context,所有的handler都被装成AbstractChannelHandlerContext使用的,这里可以将其理解默认的两个handler就行了:

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

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

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

这个HeadContext是起一个牵头作用,所以基本实现了所有方法:

//DefaultChannelPipeline.java
    final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {

        private final Unsafe unsafe;

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }

        @Override
        public ChannelHandler handler() {
            return this;
        }

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) {
            // NOOP
        }

        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) {
            // NOOP
        }

        @Override
        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
            unsafe.bind(localAddress, promise);
        }

        @Override
        public void connect(
                ChannelHandlerContext ctx,
                SocketAddress remoteAddress, SocketAddress localAddress,
                ChannelPromise promise) {
            unsafe.connect(remoteAddress, localAddress, promise);
        }

        @Override
        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
            unsafe.disconnect(promise);
        }

        @Override
        public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
            unsafe.close(promise);
        }

        @Override
        public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
            unsafe.deregister(promise);
        }

        @Override
        public void read(ChannelHandlerContext ctx) {
            unsafe.beginRead();
        }

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            unsafe.write(msg, promise);
        }

        @Override
        public void flush(ChannelHandlerContext ctx) {
            unsafe.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            ctx.fireExceptionCaught(cause);
        }

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) {
	        invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) {
            ctx.fireChannelUnregistered();

            // Remove all handlers sequentially if channel is closed and unregistered.
            if (!channel.isOpen()) {
                destroy();
            }
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            ctx.fireChannelInactive();
        }

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

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.fireChannelReadComplete();

            readIfIsAutoRead();
        }

        private void readIfIsAutoRead() {
            if (channel.config().isAutoRead()) {
                channel.read();
            }
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
            ctx.fireUserEventTriggered(evt);
        }

        @Override
        public void channelWritabilityChanged(ChannelHandlerContext ctx) {
            ctx.fireChannelWritabilityChanged();
        }
    }

可以看到一大片的ctx.fireXXX方法,这就代表着会调用下一个handler中的XXX方法。
转回AbstractChannelHandlerContext可以看到fireXXX方法的:

//AbstractChannelHandlerContext.java
    @Override
    public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED));
        return this;
    }

可以看到又调回之前的静态方法,但传入不再是head,而是使用findContextInbound方法找到下一个AbstractChannelHandlerContext,并调用它的相关XXX方法:

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

这样就形成了一个完整调用路线,从起始的HeadContext,到中间自行添加的Handler,再到最后的TailContext,所以每个方法理论上都会每个被添加的Handler中被调用。

小结一下,就是每个channel都会属于自己的pipeline,pipeline上会存在HeadContext–>自行添加的Handler–>TailContext这样的调用链,每次pipeline的fireXXX操作都会由HeadContext牵头沿着调用链一直调用到TailContext的相关方法
这也是为什么自行添加的Handler中,覆写的fireXXX方法中往往需要再次实现super.fireXXX或ctx.fireXXX的原因,因为中间的链条断了,后面的Handler包括TailContext就不会再被调用了。

其他类似的方法都是通过这种方法被调用的,起始都是head,被pipeline调用头,然后依次传递给所有handler。
再比如channelRead,读取数据,读取的地方自然是在开启的线程NioEventLoop中:

   @Override
    protected void run() {
        for (;;) {
            try {
                try {
                    switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));

                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    rebuildSelector0();
                    handleLoopException(e);
                    continue;
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
			//省略
        }
    }

开启的线程会一直读取channel的数据,然后通过一系列的调用来到类似这种:

//NioEventLoop.java
    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();

        try {
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }

            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

这里的unsafe.read()会在NioByteUnsafe中实现,并调用pipeline的fireChannelRead方法,然后依次传递消息:

        @Override
        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.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());

                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }
userEventTriggered的调用

从之前的源码可以大致了解handler中各种方法被调用的地方及时机,然后我们只要依葫芦画瓢找到userEventTriggered方法的调用就行了。
唯一不同的是,这个方法是只有在我们添加了IdleStateHandler之后才能触发的:

        ch.pipeline().addLast(new IdleStateHandler(10, 10, 10));

三个参数的意义分别是读超时,写超时,读写皆超时的时间:

    public IdleStateHandler(
            int readerIdleTimeSeconds,
            int writerIdleTimeSeconds,
            int allIdleTimeSeconds) {

        this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
             TimeUnit.SECONDS);
    }

与Mina会对每条session的读写时间都做记录不同,Netty的读写时间的记录是全部放在IdleStateHandler中的,至少超时时间是这样记载的。
另外和Mina不同的时,IdleStateHandler的传入三个超时时间,会作为延时任务的延时时间,这样会减少任务执行的次数。

Mina中是随时有一个Processor去刷新当前时间,并将当前传入判断的方法,然后通过与上次IO的记录时间进行对比,来判断是否已经超时,也就是说,是一直处于工作状态的;
而Netty是根据传入的超时时间作为检查周期,每过一个周期检查一次;

IdleStateHandler内置了三个延时任务:

  • ReaderIdleTimeoutTask
  • WriterIdleTimeoutTask
  • AllIdleTimeoutTask

看命名就知道意思了,这三个任务在channelActive时就被调用:

//IdleStateHandler.java
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        initialize(ctx);
        super.channelActive(ctx);
    }

    private void initialize(ChannelHandlerContext ctx) {
        // Avoid the case where destroy() is called before scheduling timeouts.
        // See: https://github.com/netty/netty/issues/143
        switch (state) {
        case 1:
        case 2:
            return;
        }

        state = 1;
        initOutputChanged(ctx);

        lastReadTime = lastWriteTime = ticksInNanos();
        if (readerIdleTimeNanos > 0) {//读取超时任务的执行与刷新
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (writerIdleTimeNanos > 0) {//写入超时任务的执行与刷新
            writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                    writerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (allIdleTimeNanos > 0) {//读定均超时任务的执行与刷新
            allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                    allIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
    }

    ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
        return ctx.executor().schedule(task, delay, unit);
    }

这就代表着,从channelActive开始算起,会根据传入的超时时间间隔后,延时调用一次对应的任务。
那么首次均按传入的超时时间进行各自任务的延时调用,这里传入了10、10、10,代表着在channelActive调用后10秒,这三个任务才会被首次调用。

以ReaderIdleTimeoutTask为例:

    long ticksInNanos() {
        return System.nanoTime();
    }

    private final class ReaderIdleTimeoutTask extends AbstractIdleTask {

        ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {
            long nextDelay = readerIdleTimeNanos;//计算下一次调用延时时间,初始值即为传入的超时时间
            if (!reading) {
                nextDelay -= ticksInNanos() - lastReadTime;
            }

            if (nextDelay <= 0) {
                // Reader is idle - set a new timeout and notify the callback.
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                	//产生事件
                    IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Read occurred before the timeout - set a new timeout with shorter delay.
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
        ctx.fireUserEventTriggered(evt);
    }

readerIdleTimeNanos就是传入的超时时间,单位转为了nano也就是纳秒;
lastReadTime则是上次读取数据的记录的时间,在初始化时被赋值(channelActive的调用时);
nextDelay则是最核心的下一次任务的延时时间,仅在nextDelay<=0的情况才会去调用channelIdle并触发事件;

以传入的10s为例,起初会在channelActive后间隔10s调用这个任务时,如果此时没有进行读取操作或已经读取完毕,即reading=false,

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
            reading = true;
            firstReaderIdleEvent = firstAllIdleEvent = true;
        }
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
            lastReadTime = ticksInNanos();
            reading = false;
        }
        ctx.fireChannelReadComplete();
    }

那么

        nextDelay -= ticksInNanos() - lastReadTime;

根据reading=false就会有两种情况,

  1. 如果从来未进行过读取数据的操作,那么lastReadTime从未被刷新,得到的值应该正好(理论上,忽略线程启动耗时,如果不忽略,那么右边的值将会更大,最终结果将会更小)是10 -= 10,nextDelay为0,也就符合下面的nextDelay <= 0的条件,然后开始下一次间隔为10s的任务,并使用channelIdle方法传递IdleEvent结果,也就是这里触发了fireUserEventTriggered方法;并且我们可以推知在一直未有数据读取的情况下,nextDelay得出的值会越来越小
  2. 如果是已经读取数据完毕,那么lastReadTime已经在某个时间点刷新过,得到的值就是应该10 -= x,x这个值便取决于上一次读取完毕的时间点与上一次延时任务的启动时间点;比如在channelActive调用后第3秒发生了数据读取并完毕,且后续再无读取,那么当这个延时任务调用时,x=ticksInNanos() - lastReadTime=10-3=7,那么最终nextDelay=10-7=3;

显然reading=false且nextDelay>0即第二种情况并不会满足nextDelay<=0,也就是会执行else中的代码:

        readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);

假如nextDelay为3s,那么就代表着3s后会再来检查一次;
假如这3秒内再无数据读取,那么再次执行时,x=ticksInNanos() - lastReadTime=10,nextDelay<=0会满足条件,又走上了触发事件的正轨。

如果正好当前正在读取数据,reading=true,那么情况与上述的reading=false的第二种情况类似,只是nextDelay的值不会有任何改变,还是10s,不会触发fireUserEventTriggered方法,并在10s后再执行此任务。这种情况如果不是数据量太大,一般不会遇上。

小结一下,读取检测延时任务ReaderIdleTimeoutTask会在执行时计算当前时间与上一次读取时间的差值x,如果x大于设定的超时时间间隔readerIdleTimeNanos,那么就触发读取超时事件,并设定下一次延时任务的延时时间为readerIdleTimeNanos;如果x小于readerIdleTimeNanos,那么就设定下一次延时任务的延时时间为readerIdleTimeNanos-x,并在下一次任务执行时再次判断x大小。以此循环。

其他两个延时任务采用相同的设定逻辑,就是利用这种逻辑,实现了超时机制。
最后还是上一张图,凑合看:
Bootstrap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值