Netty服务端流程源码分析(Netty4.1.25)

Netty的4个重要内容

通过阅读Netty的相关实现,刚好看到网上的一篇文章给的总结,觉得挺适合作为Netty的核心内容来研究的
1.Reactor线程模型:高性能多线程设计思路
2.Netty中自己定义的channel概念:增强版的NIOchannel
3.ChannelPipeline责任链设计模式:事件处理机制
4.内存管理:增强型byteBuf缓冲区

Server端启动相关代码例子

public static void main(String[] args) throws Exception {
    EventLoopGroup pGroup = new NioEventLoopGroup(1);
    EventLoopGroup cGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap();
    b.group(pGroup, cGroup)

            // Server端 NioServerSocketChannel.class,Client 端 NioSocketChannel.class
            .channel(NioServerSocketChannel.class)

            // options.put(option, value)
            .option(ChannelOption.SO_BACKLOG, 1024)

            // childOptions.put(childOption, value);
            .childOption( ChannelOption.SO_TIMEOUT, 20000)

            // 设置 handler 属性,
            .handler(new LoggingHandler(LogLevel.INFO))

            // 设置 childHandler 属性(多个可以用 ChannelInitializer 来设置)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel sc) throws Exception {
                    sc.pipeline().addLast(new NettyMessageDecoder<>(AttachmentRequest.class, ProtostuffSerializer.class, 1 << 20, 2, 4));
                    sc.pipeline().addLast(new NettyMessageEncoder<>(ResponseBean.class, ProtostuffSerializer.class));
                    sc.pipeline().addLast(new ServerHandler());
                }
            });

    ChannelFuture cf = b.bind(8765).sync();

    cf.channel().closeFuture().sync();
    pGroup.shutdownGracefully();
    cGroup.shutdownGracefully();

}

NioEventLoopGroup 创建以及初始化工作

我们通常会通过下面代码来实例化两个 Group

EventLoopGroup pGroup = new NioEventLoopGroup(1);

EventLoopGroup cGroup = new NioEventLoopGroup();

当我们通过 new NioEventLoopGroup() 实例化一个 NioEventLoopGroup 对象时,由于继承关系 NioEventLoopGroup --> MultithreadEventLoopGroup --> MultithreadEventExecutorGroup。通过 NioEventLoopGroup 的构造器调用链,我们直接来到其父类 io.netty.util.concurrent.MultithreadEventExecutorGroup 构造器中进行分析相关实现逻辑

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) {
               // 关闭资源
                }
            }
        }
    }

    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);
}

NioEventLoopGroup 线程数

初始化NioEventLoopGroup时,如果我们设置线程数,则会根据我们设置的线程数来在需要的时候创建线程,如果不设置,则首先根据如下代码获取可创建的线程数:

SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2);
//首先是检查我们是否配置了 io.netty.eventLoopThreads 参数,如果没配置则根据当前 JVM 可使用的 CPU 数量乘以2 来初始化线程数。

1、初始化 executor 变量

NioEventLoopGroup创建过程中,会递归调用父类的构造器完成相应的初始化操作,在父类 MultithreadEventExecutorGroup 中会执行一个 executor变量的初始化,此变量是一个 ThreadPerTaskExecutor 类型,也就是需要实例化一个 ThreadPerTaskExecutor 对象赋值给 executor,而实例化 ThreadPerTaskExecutor 时需要传入一个 ThreadFactory 对象作为构造参数,在 MultithreadEventExecutorGroup 类中是通过实例化 DefaultThreadFactory 对象作为 ThreadFactory 参数传入的。

就如 ThreadPerTaskExecutor 名称所示,只要我们调用 execute(Runnable command) 方法来提交任务,此executor就会新建一个线程来执行任务。

2、初始化 children 数组并完成各个元素的实例化

  1. 根据线程数来创建一个 children 数组(长度为线程数),数组类型是 EventExecutor 类型的;
  2. 循环为每个元素初始化一个 NioEventLoop 对象(由子类来创建EventLoop 对象);需要传入两个参数,一个是 上一步始化好的 executor 对象,一个是 NioEventLoopGroup 中传来的参数信息,包括的元素为: SelectorProvider,SelectStrategyFactory ,RejectedExecutionHandler 此三个元素。在 NioEventLoop 构造方法被调用时(包括父类构造方法)主要做了如下工作:
  • 【1】在超类 SingleThreadEventLoop 中创建了一个引用为 tailTasks 的 LinkedBlockingQueue 队列。

  • 【2】 在超类 SingleThreadEventExecutor 中也创建了 一个引用为 taskQueue 的 LinkedBlockingQueue 队列。

  • 【3】 NioEventLoop 把 所属的 NioEventLoopGroup 对象以 parent 变量传给超类,并存入超类的 parent 变量中。

  • 【4】初始化 Nio Selector 对象信息,

  • 【5】初始化 NioEventLoop 的 executor 信息,此信息是公用 MultithreadEventExecutorGroup 中的 executor 实例(复用)所以后面逻辑 每个 NioEventLoop 都各自执被调用 execute 提交任务并启动 EventLoop线程来执行 selector.select() 获取就绪事件并调用 pipeline中的 handler进行处理,以及执行 taskQueue中的方法(也就是各自启动一个线程来执行自己的 selector 事件 以及执行自己 taskQueue中的任务);

其中上面的两个队列长度,最小为 16 ,最大为 Integer.MAX , 取决于 io.netty.eventLoop.maxPendingTasks 参数

DEFAULT_MAX_PENDING_TASKS = Math.max(16, SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE))

以上 SystemPropertyUtil.getInt( v1, v2) : 如果v1 不为空返回 v1, 否则范湖 v2;

3、初始化 chooser 对象

chooser 对象是根据上一步初始化好的 children 数组长度来创建一个 EventExecutorChooser 对象;其中根据 children 数组元素个数是否是 2的N 次方来看是实例化 PowerOfTowEventExecutorChooser 还是实例化 GenericEventExecutorChooser ;其中两个功能都是一样,根据任务数取余来获取 children 中的 NioEventLoop 对象;但当 children 的元素个数是 2 的 N 次方时可以根据“与运算” 来取余,速更度快。

4、为 children 数组中的每个 NioEventLoop 添加一个 terminationListener

首先是创建一个 FutureListener 实例作为 terminationListener ,然后循环 children 为每个元素(的 terminationListener )注册一个FutureListener ,此监听器的实现就是当当 terminatedChildren 数值等于 children.length 的时候,则调用 terminationFuture.setSuccess(null) ,具体作用和具体什么场景下调用应该是终止程序有关。

其中 e.terminationFuture() 返回的是 NioEventLoop 的父类 SingleThreadEventExecutor 中的 terminationFuture 值。

5、最后完成 readonlyChildren 的初始化

最后剩余的代码就是根据 children 中的元素来生成一个不可变的 Set 集合,并赋值给 readonlyChildren 变量,方便后续的遍历

ServerBootstrap 初始化以及启动流程分析

ServerBootstrap创建以及初始化工作

ServerBootstrap 创建时,会完成相关全局变量的初始化(包括一些集合等信息)

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);

private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();

private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

private volatile EventLoopGroup childGroup;

private volatile ChannelHandler childHandler;

public ServerBootstrap() { }

当我们调用 ServerBootstrap 默认构造参数来创建 ServerBootstrap 对象时,会执行全局参数的初始话, 包括父类的 options 和 attrs 集合信息(也就是为 childGroup parentGroup 分别初始化好这些集合信息),这些集合为我们后面传入的初始化信息做保存的,因此后面我们就可以通过调用 ServerBootstrap 相关方法来完成相应属性的初始化工作,类似;

ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)

        // Server端 NioServerSocketChannel.class,Client 端 NioSocketChannel.class
        .channel(NioServerSocketChannel.class)

        // options.put(option, value)
        .option(ChannelOption.SO_BACKLOG, 1024)

        // childOptions.put(childOption, value);
        .childOption( ChannelOption.SO_TIMEOUT, 20000)

        // 设置 handler 属性,
        .handler(new LoggingHandler(LogLevel.INFO))

        // 设置 childHandler 属性(多个可以用 ChannelInitializer 来设置)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            protected void initChannel(SocketChannel sc) throws Exception {
                sc.pipeline().addLast(new NettyMessageDecoder<>(AttachmentRequest.class, ProtostuffSerializer.class, 1 << 20, 2, 4));
                sc.pipeline().addLast(new NettyMessageEncoder<>(ResponseBean.class, ProtostuffSerializer.class));
                sc.pipeline().addLast(new ServerHandler());
            }
        });

调用 Bootstrap#bind(int) 方法完成相应信息的绑定以及注册

对应例子中的如下bind() 方法

ChannelFuture cf = b.bind(8765).sync()

最终调用到 io.netty.bootstrap.AbstractBootstrap#doBind 方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        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) {
                    // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                    // IllegalStateException once we try to access the EventLoop of the Channel.
                    promise.setFailure(cause);
                } else {
                    // Registration was successful, so set the correct executor to use.
                    // See https://github.com/netty/netty/issues/2586
                    promise.registered();

                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

接下来我们根据源码进行分析

NioServerSocketChannel的创建以及EventLoop的注册

在doBind() 方法中通过调用 initAndRegister() 来完成Channel 的初始化以及完成Channel到EventLoop的注册逻辑

1. 根据 channelFactory创建一个 Channel 实例,服务端这里对应的是 NioServerSocketChannel 实例

  • 【1】在创建 NioServerSocketChannel 时会根据 java.nio.channels.spi.SelectorProvider#openServerSocketChannel 创建一个 ServerSocketChannel 实例,此实例信息被记录在父类 AbstractNioChannel 的 ch 变量中(后续可调用 AbstractNioChannel#javaChannel() 获取),并设置 ch.configureBlocking(false) 以及 readInterestOp = SelectionKey.OP_ACCEPT

  • 【2】同时初始化 unsafe 变量以及 pipeline = new DefaultChannelPipeline(this);

  • 【3】 调用 config = new NioServerSocketChannelConfig(this, javaChannel().socket()) 完成 config的初始化,此初始化会创建一个 AdaptiveRecvByteBufAllocator 实例来为 channel 管理 ByteBuffer信息(扩容缩容等);

2. 调用ServerBootstrap#init( channel ) 初始化Channel信息

  • 【1】根据 options 信息以及 attrs 信息初始化NioServerSocketChannel 对象信息(以备 NIO channel使用)

  • 【2】调用 channel.pipeline() 获取上面创建的 pipeline 对象 (DefaultChannelPipeline ),然后调用 pipeline 的 addLast( ChannelHandler )方法首先添加 bootstrap 中配置的 handler 信息,然后提交一个任务到 channel eventLoop 中,此任务是为 当前Pipeline 添加一个 ServerBootstrapAcceptor 类型的Handler信息,整体代码为:

p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) throws Exception {
        final ChannelPipeline pipeline = ch.pipeline();
        ChannelHandler handler = config.handler();
        if (handler != null) {
            pipeline.addLast(handler);
        }

        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                pipeline.addLast(new ServerBootstrapAcceptor(
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }
});

【重点】在 DefaultChannelPipeline#addLast( ChannelHandler)方法中,首先会根据当前的 ChannelHandler来创建 ctx 实例并插入到 pipeline中,同时会调用 ChannelHandler#handlerAdded() 方法,而 ChannelInitializer#handlerAdded() 方法中又调用了 ChannelInitializer#initChannel() 方法,所以会轮训调用到 我们实现的 initChannel() 来完成相关 Handler 的加入逻辑;

以上代码中的 channel 目前还未配置上 eventLoop 变量信息,需要在下面的 register(channel) 进行注册之后才配置上 EventLoop;

3. 完成 Channel 到 EventLoop 以及对应的 Selelctor 的注册
调用 ChannelFuture regFuture = config().group().register(channel) 并返回此 regFutre 对象(DefaultChannelPromise 类型);在创建 NioEventLoopGroup 时就通过 config = new ServerBootstrapConfig(this) 初始化了 config 变量;所以此处的 config().group()获取的是注入ServerBootstrap 中的 NioEventLoopGroup 对象(return bootstrap.group()),也就是 parentGroup 信息;

  • 【1】调用 MultithreadEventLoopGroup#register(io.netty.channel.Channel) 方法,在此方法中 调用 next().register(channel) 来完成相关业务逻辑

  • 【2】next() 方法的逻辑 是调用 MultithreadEventExecutorGroup#chooser 中的 next() 方法来获取下一个 NioEventLoop 类型的 EventExecutor 信息,实现逻辑就是 chooser 内部维护一个AtomicInteger idx, 根据 idx.getAndIncrement() 来与 children 数组长度求余来获取数组中的元素并返回;实现的策略根据任务数取余来获取 children 中的 NioEventLoop 对象;但当 children 的元素个数是 2 的 N 次方时 可以根据位与运算来取余,速更度快;

  • 【3】由于 Eventloop 都继承于 SingleThreadEventLoop 所以 最终会调用 SingleThreadEventLoop#register(Channel) 来完成相关逻辑最终调用链:AbstractChannel 类中的 AbstractUnsafe#register -> SingleThreadEventExecutor#execute() --> SingleThreadEventExecutor#addTask() ; 最后返回 DefaultChannelPromise 实例信息

AbstractChannel 类中的 AbstractUnsafe#register() :初始化当前的 AbstractChannel.this.eventLoop = eventLoop , 也就是完成当前channel 到 NioEventLoop 的绑定;提交 pipeline 任务到 taskQuene 任务列表中(由于不是 eventLoop执行线程,所以以任务方式提交执行内容,也就是调用 SingleThreadEventExecutor#execute() 提交 Runnable),并通过 CAS 来启动 NioEventLoop中的线程;大概流程为:

SingleThreadEventExecutor#execute( Runnable ) --> SingleThreadEventExecutor#addTask() (如果添加task 失败则调用拒绝策略 rejectedExecutionHandler.rejected(task, this) ) ; 并且判断当前线程是不是负责执行 EventLoop 中的任务(SingleThreadEventExecutor#taskQueue)的线程,如果是则直接退出,否则调用 SingleThreadEventExecutor#startThread() 通过 CAS来尝试启动 SingleThreadEventExecutor 中的线程(如果启动过了则不执行任何逻辑,否则通过向 ThreadPerTaskExecutor 提交一个 Runable 来启动线程执行任务信息)—— 所以如上提到,虽然 SingleThreadEventExecutor 每次提交任务都会启动一个新线程来执行,但一个 SingleThreadEventExecutor 只会提交一次(CAS成功才提交),并且 SingleThreadEventExecutor 中的线程运行之后会一直循环拉取 selector中就绪的事件和 taskQueue中的任务,直到异常或者程序终止;

再次总结上面的执行的逻辑:

  • (1)完成 channel 到给定的(通过 chooser 选择得到的) eventLoop 的绑定任务;
  • (2)提交Runnable 任务到 eventLoop 的 taskQuene中, 并通过 CAS 检查是否需要启动 eventLoop 的线程(线程会运行 NioEventLoop#run()),如果未启动则进行启动,否则忽略;

io.netty.channel.AbstractChannel.AbstractUnsafe#register 代码如下:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// ........异常检查等逻辑删除
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
        }
    }
}

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

        // 完成 nio channel 到 nio selector 的注册,其中兴趣事件值为0
        doRegister();
        neverRegistered = false;
        registered = true;
        // 会检查是否有 pending Handler 完成 handlerAdded 方法的调用,也就是确保标记channel 为 registration 之前确保所有 handler 都加入到 pipeline中
        pipeline.invokeHandlerAddedIfNeeded();
       
         // 标记此 future为成功状态,并通知listeners列表,把  io.netty.util.concurrent.DefaultPromise#result 字段标记为 SUCCESS
        safeSetSuccess(promise);
        // 传播 channelRegistered事件(追踪主流程handler(head + tail ),没发现做什么处理)
        pipeline.fireChannelRegistered();
        //  isActive() 逻辑是判断是否已完成端口号的绑定,如果已完成则完成 channel active 事件在 pipeline 上的传播.
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                // See https://github.com/netty/netty/issues/4805
                beginRead();
            }
        }
    } catch (Throwable t) {
}
  • 【4】channel 到 selector 的绑定

其中 register0 方法中的 doRegister() 方法是完成 nio Channel 到 selector 的注册逻辑,此处注册的兴趣事件为 0 ,并把当前channel 作为 attach 信息:

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

isActive() 逻辑是判断是否已完成端口号的绑定,如果已完成则执行 channel active 事件在 pipeline 上的传播.由于绑定端口号是在后面逻辑完成,所以此处暂不分析,也就是到目前,channel 虽然注册到selector 上了,但注册的兴趣事件是空的,也就是 ops 为0,后续我们再找找在哪里注册上了 ACCPT事件(值为16);

至此 final ChannelFuture regFuture = initAndRegister() 逻辑分析完毕;

调用 doBind0(regFuture, channel, localAddress, promise) 完成端口号的绑定

首先是根据 regFuture.isDone() 来判断当前Future 是否已执行完成,如果已完成则直接调用 dobind0() 方法进行端口号的绑定,否则添加一个 pending 任务,把绑定逻辑传给 regFuture 的监听列表,等Futrue 逻辑处理完成后再进行监听列表的回调来完成绑定逻辑的调用:由于上面 register0() 中已调用 safeSetSuccess(promise) 把 futrue result 标记为 SUCCESS, 所以此处的 isDone() 返回true, 也就是直接调用 doBind0(regFuture, channel, localAddress, promise) 进行端口号的绑定, 然后返回 promise 对象。

if (regFuture.isDone()) {
    // At this point we know that the registration was complete and successful.
    ChannelPromise promise = channel.newPromise();
    doBind0(regFuture, channel, localAddress, promise);
    return promise;
} else {
    // Registration future is almost always fulfilled already, but just in case it's not.
    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) {
                // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                // IllegalStateException once we try to access the EventLoop of the Channel.
                promise.setFailure(cause);
            } else {
                // Registration was successful, so set the correct executor to use.
                // See https://github.com/netty/netty/issues/2586
                promise.registered();

                doBind0(regFuture, channel, localAddress, promise);
            }
        }
    });
    return promise;
}

而 doBindo() 方法的逻辑就是给当前 channel 的 eventLoop 提交一个 bind 任务,也就是调用 SingleThreadEventExecutor#execute( Runnable ) 提交一个任务;任务功能是完成端口号的绑定,调用任务链为:

DefaultChannelPipeline#bind(SocketAddress, ChannelPromise) ---> AbstractChannel#bind(SocketAddress,ChannelPromise) --> tail.bind(localAddress, promise) ..........tail --> head方向....... --> DefaultChannelPipeline.HeadContext#bind() ----> AbstractUnsafe#bind() -->AbstractUnsafe#bind ---》 NioServerSocketChannel#doBind --》javaChannel().socket().bind(localAddress, config.getBacklog()) (Nio 的端口绑定逻辑)绑定完后,调用 promise.trySuccess() 进行标记( Marks this future as a success and notifies all listeners. )

而重要逻辑在 AbstractChannel.AbstractUnsafe#bind 方法中,源代码如下(仅留要分析的重要代码):

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    assertEventLoop();

    boolean wasActive = isActive();
    try {
        doBind(localAddress);
    } catch (Throwable t) {
    }

    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireChannelActive();
            }
        });
    }

    safeSetSuccess(promise);
}

完成端口号的绑定逻辑

首先 bind 的调用会通过 DefaultChannelPipeline ,从 tail 方向开始,流入head, 最终在 DefaultChannelPipeline.HeadContext#bind()中调用到 AbstractUnsafe#bind() 方法;

AbstractUnsafe#bind ---> NioServerSocketChannel#doBind --> javaChannel().socket().bind(localAddress, config.getBacklog()) ;

最终到 java.net.ServerSocket#bind(java.net.SocketAddress, int) 方法中,此处便是熟悉的 NIO Server 端端口号的绑定了;其实Netty对端口号的绑定也没什么复杂的逻辑,只是需要通过 pipeline 调用 bind 从 tail 方向传播到 head 方向;最终在 head 时调用 unsafe 来调用 jdk 完成 server 端口的绑定;

通过pipeline.fireChannelActive() 的调用完成 ACCEPT 事件的绑定

在之前的 channel 到 selector 的绑定中,只是注册了 ops 为0 ,也就是并绑定任何事件信息。而真正 ACCEPT 事件绑定是在此处代码才完成绑定的

上面方法中,对于 if (!wasActive && isActive()) 的判断,其实就是判断在调用 doBind 之前, javaChannel().socket().isBound() 是false, 而在调用之后,返回true, 也就是之前socket是未绑定,调用 doBind 后完成了绑定的情况 则会调用到if里面的逻辑。

也就是往 eventLoop 的任务列表中插入一条对 pipeline.fireChannelActive() 进行调用的任务;此方法会调用 AbstractChannelHandlerContext.invokeChannelActive(head) 方法,也就是从pipeline 的 head 方向往 tail 方向调用channelActive() 方法 ,而在 DefaultChannelPipeline.HeadContext#channelActive 方法中:

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

    readIfIsAutoRead();
}

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

也就是会判断 channel # config # isAutoRead 是否为true, 为true 的话则调用 channel.read() 方法。而默认则是返回 true的,所以会调用 channel read(); 也就是会从 tail.read() ----> head.read() 方向进行调用,最终在 HeadContext#read 方法中进行 AbstractUnsafe#beginRead() 方法的调用,最终调用 AbstractNioChannel#doBeginRead,源码如下:

@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

其中 AbstractNioChannel#readInterestOp 变量在上面我们也有提到过,在实例化 NioServerSocketChannel 时会设置 ch.configureBlocking(false) 以及 readInterestOp = SelectionKey.OP_ACCEPT;因此也就在此步完成了 channel ACCEPT 事件的绑定;

调用 safeSetSuccess(promise) 执行 future SUCCESS 标记

此逻辑在上面也大概提过,就是把promise 的 result 尝试标记为 CUCCESS并完成此 future中的 listeners 列表的信息的回调; 也就是上面提到的调用 promise.trySuccess() 进行标记( Marks this future as a success and notifies all listeners. )

NioEventLoop#run() 代码逻辑分析

每个EventLoop 都会对应一个线程,并在线程中 for 循环:

  1. 拉取自己 Selector 上的 Channel 事件信息(READ、WRITE、 ACCEPT、 CONNECTION);

  2. 调用 processSelectedKeys() 处理 Channel 事件,包括传播到 channel 对应的 pipeline 中由各个 handler 处理

  3. 执行 taskQueue 中的任务,确保提交的任务都有机会执行

io.netty.channel.nio.NioEventLoop#run() 源码如下(部分已删除):

@Override
protected void run() {
    for (;;) {
        try {
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // fall through
                default:
            }
            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);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

下面我们进一步具体分析各个步骤

执行select() 拉取准备事件

首先根据任务列表(taskQueue、tailTasks)是否为空来执行 select() 的策略,执行完select() ,主要是执行 selecteNow() 还是 select( timeOut), 以及确定 timeOut 的时间(默认是一秒)

调用 processSelectedKeys() 处理就绪事件

1、获取事件

在 NioEventLoop#openSelector() 方法中,在创建 nio selector 对象时,nettty 通过判断 io.netty.noKeySetOptimization 参数来决定是否执行 selector 的相关优化功能,如果执行优化操作的话,netty会通过 反射把 SelectorImpl#publicKeys 和 SelectorImpl#publicSelectedKeys 对象置换成自己的 SelectedSelectionKeySet 对象,然后把 nio selector 封装成一个 SelectedSelectionKeySetSelector 对象,具体优化可以查看相关代码,由于默认没配置,所以此处不进行优化代码逻辑的剖析;

在执行完 select() 操作之后就可以通过 selector.selectedKeys() 来获取就绪事件的集合,接着执行事件的处理逻辑,对应的 NioEventLoop#processSelectedKeysPlain() 的部分源码如下 :

private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
    Iterator<SelectionKey> i = selectedKeys.iterator();
    for (;;) {
        final SelectionKey k = i.next();
        final Object a = k.attachment();
        i.remove();

        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }
        if (!i.hasNext()) {
            break;
        }
    }
}

首先判断 SelelctionKey # attachment() 对象是否属于 AbstractNioChannel 类型,其中在上面我们已经分析过 (其中 register0 方法中的 doRegister() 方法是完成 nio Channel 到 selector 的注册逻辑,并把当前channel 作为 attach 信息),所以此处的 attachment 是属于 AbstractNioChannel 类型的,所以会走到 NioEventLoop#processSelectedKey( SelectionKey, AbstractNioChannel) 的方法逻辑:


private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    if (!k.isValid()) {
        // 略.......
        return;
    }

    try {
        int readyOps = k.readyOps();
        // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
        // the NIO JDK channel implementation may throw a NotYetConnectedException.
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
            // See https://github.com/netty/netty/issues/924
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);

            unsafe.finishConnect();
        }

        // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
            ch.unsafe().forceFlush();
        }

        // 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();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

OP_CONNECT是客户端事件,OP_WRITE 是我们给channel写入数据时触发的事件(目前我们也没有注册 WRITE事件),所以此处我们只分析最后一种情况,也就是 OP_READ | OP_ACCEPT 事件的情况;因此走到 unsafe.read() 方法的调用。

2、根据事件获取信息

首先我们先来看看 channel 的 unsafe.read(), unsafe 有两种实现(为了兼容各种数据类型,pipeline数据传递使用了 Object 作为数据类型):
NioByteUnsafe # read :此实现逻辑是通过channel 来读取数据信息,然后在pipeline中传播 channelRead( Object ) 事件和 channelReadComplete()事件,也就是真正读取 socket buffer 中的数据信息 并从 pipelien 的 head 往 tail 方向传播读取的数据信息,;
NioMessageUnsafe # read: 此实现并不是从 channel 中读取事件信息,而是通过 ServerSocketChannel accept 来获取 SocketChannel, 并把 SocketChannel 封装成一个 NioSocketChannel 放入readBbuffer 数组中,接着再获取出来,然后同样在pipeline中传播 channelRead( Object ) 事件和 channelReadComplete()事件,并从 pipelien 的 head 往 tail 方向传播获取的 NioSocketChannel 对象信息,

而对于此次 Netty分析,到目前我们只是分析到 Server 端 ParentGroup 启动过程,在上面的 unsafe.read() 方法调用的其实是 NioMesaageUnsafe # read 的方法,依据是:unsafe 是通过调用 channel#unsafe() 获取的,而我们此处的 channel 对应的是 NioServerSocketChannel, 而 NioServerSocketChannel 的 unsafe 是在父类 AbstractNioMessageChannel 中创建的,也就是AbstractNioMessageChannel.NioMessageUnsafe 类型的unsafe, 所以调用链为 NioMessageUnsafe#read() --> NioServerSocketChannel#doReadMessages() , 对应 NioServerSocketChannel#doReadMessages() 源码如下(删减):

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) { 
    } 
    return 0;
}

以上我们便分析完了信息的获取逻辑,下面我们分析创建NioSocketChannel 中做了哪些工作;

3、NioSocketChannel 的创建

在上面逻辑中我们已经分析了 NioServerSocketChannel 创建的相关逻辑,其实NioSocketChannel 的创建同样也需要完成相关功能的创建以及初始化工作,下面我们简单对比一下相关逻辑:

channel.png

从类的关系图中可以看出 NioSocketChannel 继承自 AbstractNioByteChannel , NioServerSocketChannel 继承自 AbstractNioMessageChannel, 而这两个 AbstractXXXChannel 都继承自 AbstractNioChannel;

  • AbstractNioByteChannel : 内部维护了一个 NioByteUnsafe,通过此 unsafe 来为外部提供 read、write 方法的调用,然后此read/write 方法会调用AbstractNioByteChannel 的 doReadBytes / doWriteBytes 方法(抽象方法),而在 NioSocketChannel 类中对这两方法进行了实现,大概实现逻辑就是从 SocketChannel 中读取网络传入的数据信息或者写入数据信息;上面说的NioByteUnsafe # read 所提供的功能正是在AbstractNioByteChannel.doReadBytes () 中进行实现的;

  • NioServerSocketChannel: 内部维护了一个 NioMessageUnsafe,同样通过此 unsafe 来给外部提供 read、write 方法的调用,不同的是 NioServerSocketChannel 为 unsafe 提供的是 doReadMessages / doWriteMessage 方法,对于 doReadMessages 方法的功能,正是我们上面说的 NioMessageUnsafe # read 功能,也就是 通过 serverSocket.accept() 获取到 SocketChannel 对象的功能,而对于 doWriteMessage , NioServerSocketChannel 实现是抛出 UnsupportedOperationException 异常, 也就是不支持调用;

  • config : NioSocketChannelConfig#NioSocketChannelConfig 中维护的信息是 NioSocketChannel 和 Socket 信息,NioServerSocketChannel.NioServerSocketChannelConfig维护的是 NioServerSocketChannel 和 ServerSocket 信息 ;

  • readInterestOp: NioSocketChannel 维护的值为 OP_READ, NioServerSocketChannel 维护的值为 OP_ACCEPT。

  • configureBlocking:两个 Channel 都设置为 false,也就是都是非阻塞IO,

  • unsafe: 上面我们已经分析过了,为子类提供一个 newUnsafe() 方法,NioSocketChannel 提供 NioByteUnsafe 实现,NioServerSocketChannel 提供 NioMessageUnsafe 实现;

  • pipeline:一样的实现,都会创建一个 DefaultChannelPipeline 实例信息;并且同样完成相关的初始化工作,包括 head,tail 节点的初始化等。

以上便是 NioSocketChannel 创建时会完成相关信息的初始化工作(有的是在构造器中完成初始化,有的是通过对象变量进行初始化),下面就来看看如何把 NioSocketChannel 注册到EventLoopGroup类型的 workerGroup 中的;

4、完成NioSocketChannel 到 EventLoop 的注册工作

在上面提到的 ServerBootstrap#init( channel ) 初始化Channel信息时,会提交一个任务到 channel eventLoop 中,此任务是为 当前Pipeline 添加一个 ServerBootstrapAcceptor 类型的Handler信息,也就是为 NioServerSocketChannel 的 pipeline 添加了一个 ServerBootstrapAcceptor 类型的Handler信息,而在 AbstractNioMessageChannel.NioMessageUnsafe#read() 方法中,Netty会获取 readBuffer中的信息,并调用 pipeline.fireChannelRead( Object ) 来把信息传播给 pipeline 上的handler 去执行,源码如下:

private final class NioMessageUnsafe extends AbstractNioUnsafe {

    private final List<Object> readBuf = new ArrayList<Object>();

    @Override
    public void read() {
        assert eventLoop().inEventLoop();
        final ChannelConfig config = config();
        final ChannelPipeline pipeline = pipeline();
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.reset(config);

        boolean closed = false;
        Throwable exception = null;
        try {
            try {
                do {
                     // 根据 ACCEPT 事件获取 SocketChannel 并封装成功 NioSocketChannel 放入 readBuff 中
                    int localRead = doReadMessages(readBuf);
                    if (localRead == 0) {
                        break;
                    }
                    if (localRead < 0) {
                        closed = true;
                        break;
                    }

                    allocHandle.incMessagesRead(localRead);
                } while (allocHandle.continueReading());
            } catch (Throwable t) {
                exception = t;
            }

            int size = readBuf.size();
            for (int i = 0; i < size; i ++) {
                readPending = false;
                // 循环每一个事件信息并 传播 channelRead 方法
                pipeline.fireChannelRead(readBuf.get(i));
            }
            readBuf.clear();
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();
        } finally {
            if (!readPending && !config.isAutoRead()) {
                removeReadOp();
            }
        }
    }
}

根据上面的代码可以看出,最终会通过pipeline传播事件获取的消息,也就是NioScoketChannel 对象,因此会调用到 ServerBootstrap.ServerBootstrapAcceptor#channelRead() 方法,其对应源码如下:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;

    child.pipeline().addLast(childHandler);

    setChannelOptions(child, childOptions, logger);

    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    try {
        childGroup.register(child).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    forceClose(child, future.cause());
                }
            }
        });
    } catch (Throwable t) {
        forceClose(child, t);
    }
}

以上channelRead(ChannelHandlerContext ctx, Object msg) 方法中,msg信息就是我们提到的 NioSocketChannel 对象,通过以上代码,其主要完成以下几个功能:

  1. 为 NioSocketChannel pipeline 添加childHandler,也就是我们在写 server端代码时调用 ServerBootstrap#childHandler(ChannelHandler)配置的 handler 信息;

  2. 为 NioSocketChannel 初始化我们提供的相关配置,也是我们服务端编码时通过调用 childOption(ChannelOption<T> childOption, T value)和 childAttr(.AttributeKey<T> childKey, T value) 写入的配置信息;

  3. 完成 NioSocketChannel 到 childGropu 中 EventLoop 的注册,具体注册逻辑,跟我们上面分析的 NioServerSocketChannel 注册到 parentGriup 中的 EventLoop 的逻辑是一样的,区别就是兴趣事件的绑定,NioSocketChannel 是在注册完 EventLoop 时便已是 active状态,所以当时就会通过 pipeline传播调用 channelActive() 并在 head 中绑定 readInterestOp (OP_READ)事件到 selector中。而 NioServerSocketChannel 则是在完成端口号的绑定后才会提交一个任务来传播 channelActive 进行 readInterestOp (OP_ACCEPT)事件绑定 。

流程图

1、NioEventLoop 到 Pipeline流程图

image-pipeline.png

END其他流程图看后续有没有时间补充吧,有建议什么的随时留言我会及时回复

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350