Netty学习笔记:2.Netty启动过程源码分析

本文详细分析了Netty服务的启动过程,从创建ServerBootstrap到设置线程组、通道类型、Handler,再到绑定端口和启动服务。文章通过源码跟踪,解释了线程组如何工作,以及数据如何在Handler之间传递。最后,讨论了优雅关闭Netty服务的方法。

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

在上一篇 Netty学习笔记:1.从入门示例分析ChannelHandler的使用 中,我们使用了非常简单的入门示例,那么Netty是如何启动的呢?为什么启动后它就可以接收连接,接收数据了?数据是怎么一层层传递到各个Handler的?两个线程组是怎么工作的?带着这些问题,我们使用调试模式,一步步地跟踪Netty的整个启动过程,去寻找问题的答案。

目录

0.示例代码概述

1.创建ServerBootstrap

2.创建线程组bossGroup和workerGroup

3.设置线程组bossGroup和workerGroup

4.设置通道类型

5.设置Handler

6.设置选项option和childOption

7.绑定端口,启动服务

8.优雅地关闭netty服务


0.示例代码概述

我先把入门示例代码再贴一遍,然后逐步地分析

public class Server {

    public void start(int port) {
        new Thread("netty-server-thread") {
            @Override
            public void run() {
                super.run();
                NioEventLoopGroup bossGroup = null;
                NioEventLoopGroup workerGroup = null;
                try {
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bossGroup = new NioEventLoopGroup();
                    workerGroup = new NioEventLoopGroup();
                    bootstrap.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                                @Override
                                protected void initChannel(NioSocketChannel ch) throws Exception {
//                                    ch.pipeline().addLast(new StringDecoder());
//                                    ch.pipeline().addLast(new ServerHandler());
                                    ch.pipeline().addLast(new WriteHandler1());
                                    ch.pipeline().addLast(new WriteHandler2());
                                    ch.pipeline().addLast(new ReadHandler1());
                                    ch.pipeline().addLast(new ReadHandler2());
                                }
                            })
                            .option(ChannelOption.SO_BACKLOG, 128)
                            .childOption(ChannelOption.SO_KEEPALIVE, true);
                    System.out.println("[" + Thread.currentThread().getName() + "]" + Server.class.getSimpleName() + ": server start");
                    bootstrap.bind(port).sync().channel().closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (bossGroup != null) {
                        bossGroup.shutdownGracefully();
                    }
                    if (workerGroup != null) {
                        workerGroup.shutdownGracefully();
                    }
                }

            }
        }.start();
    }
}

Netty服务就是靠Bootstrap启动类启动的,先给它设置了两个线程组,bossGroup用于监听客户端的连接、读写事件,workerGroup用于处理连接、读写事件。然后设置了此次使用的Channel类型是NioServerSocketChannel,即非阻塞的服务端Channel类型,感兴趣的读者请自行百度BIO和NIO的区别。接着设置了childHandler,就是处理连接、读写事件的处理类,他们会在workerGroup线程组的线程池里运行。option和childOption就是一些参数设定,这里不展开了。最后的一步,也是最关键的一步,前面的步骤都是一些初始化设置的东西,到这一步才是真正的启动服务,如绑定端口,启动线程组,监听事件等。closeFuture().sync()是阻塞线程,等待服务关闭,因为所有的客户端连接、读写事件都在bossGroup和workerGroup里执行,所以这个线程阻塞并不影响业务的运行。也正是因为这个线程会在最后阻塞,所以Server类在start的开始,就创建了一个新的线程new Thread()来执行netty服务的启动。finally里的代码,只有等到服务关闭,即等到closeFuture().sync()阻塞完后才会执行,因为服务关闭了,所以要关闭这两个线程组,以免造成资源浪费或者可能导致内存泄漏。

示例代码讲解完后,接下来我们从源码角度逐步分析示例代码的各个环节。

1.创建ServerBootstrap

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

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

    private final Map<ChannelOption<?>, Object> childOptions = new ConcurrentHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;

    public ServerBootstrap() { }

//其他代码...

}

logger就是一个日志框架,后续netty的信息输出都会使用到这个logger打印日志,日志框架不在本章讨论范围,感兴趣的读者可以百度之,比如slf4j,log4j,logback等主流日志框架。

然后是初始化用于保存选项的childOptions和保存属性的childAttrs,因为ServerBootstrap继承于AbstractBootstrap,所以也会初始化AbstractBootstrap类的一些属性,如下面代码段中的options和attrs

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {

    volatile EventLoopGroup group;
    @SuppressWarnings("deprecation")
    private volatile ChannelFactory<? extends C> channelFactory;
    private volatile SocketAddress localAddress;
    private final Map<ChannelOption<?>, Object> options = new ConcurrentHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
    private volatile ChannelHandler handler;

    AbstractBootstrap() {
        // Disallow extending from a different package.
    }

//其他代码...

}

然后创建一个ServerBootstrapConfig的配置类

public final class ServerBootstrapConfig extends AbstractBootstrapConfig<ServerBootstrap, ServerChannel> {

    ServerBootstrapConfig(ServerBootstrap bootstrap) {
        super(bootstrap);
    }

    public EventLoopGroup childGroup() {
        return bootstrap.childGroup();
    }

    public ChannelHandler childHandler() {
        return bootstrap.childHandler();
    }

    public Map<ChannelOption<?>, Object> childOptions() {
        return bootstrap.childOptions();
    }

    public Map<AttributeKey<?>, Object> childAttrs() {
        return bootstrap.childAttrs();
    }

}

这个类的构造器是将ServerBootstrap对象传进去,里面提供了一些获取options, attrs, childOptions, childAttrs等属性的方法,这个其实我挺疑惑的,这个config在ServerBootstrap的声明是private的,说明不是提供给外界使用的,然而options, attrs, childHandler等这些属性,ServerBootstrap本身就可以获取到,这个config感觉有点多此一举了。

2.创建线程组bossGroup和workerGroup

这两个对象,其实都是NioEventLoopGroup,所以这里只讲一遍bossGroup的创建过程

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {

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

    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

//其他代码...

}

NioEventLoopGroup继承于MultithreadEventLoopGroup,所以第一步执行MultithreadEventLoopGroup里的静态属性和静态代码块,首先也是初始化一个logger,然后静态代码块里,初始化了一个DEFAULT_EVENT_LOOP_THREADS的整数,即默认线程数量,这里它的取值是有效核心处理器的数量乘以2,我本地调试的结果是12,那说明我的电脑CPU是六核的,于是去中关村搜了一下CPU的型号,确实是六核的(来自程序猿的严谨态度,哈哈)

初始化完父类MultithreadEventLoopGroup的静态代码后,回到了NioEventLoopGroup本身的构造器,它有多个构造器,逐个逐个地调用,如下所示

public class NioEventLoopGroup extends MultithreadEventLoopGroup {

    public NioEventLoopGroup() {
        this(0);
    }

    public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
    }

    public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
    }

    public NioEventLoopGroup(
            int nThreads, Executor executor, final SelectorProvider selectorProvider) {
        this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
    }

    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

//其他代码...

}

一直调用到最后一个构造器,就会调用到父类的构造器,如下所示

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {

//...

    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

//...

}

因为我们示例代码是使用了不带参数的构造器,所以使用的线程数量为0,到了父类构造器这里,如果是0,就会采用之前初始化的默认线程数量,之前我们调试得到的数量是12。在实际的项目应用中,我们可以根据系统的业务大小调整线程数量,比如你为公司做了一个简单的考勤系统,公司只有二三十个人,而且考勤系统基本上只有上下班的时间才会使用,那么bossGroup和workerGroup各使用12个线程真的非常浪费资源,对于这种用户少,并发量低的系统,完全可以使用一两个线程就可以应付了。你可以像如下设置创建两个线程组

public class Server {

    public void start(int port) {
//...
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bossGroup = new NioEventLoopGroup(1);
                    workerGroup = new NioEventLoopGroup(2);
//...
    }
}

MultithreadEventLoopGroup的构造器继续调用它的父类MultithreadEventExecutorGroup的构造器,传参executor为空,所以这里会为它创建类型为ThreadPerTaskExecutor的执行器,顾名思义就是一个任务对应一个线程的执行器,newDefaultThreadFactory内部会为以后创建的线程名称设定前缀为nioEventLoopGroup-2-,比如我本地默认线程数量是12,所以之后如果启用线程组并创建线程,那么里面的线程名称就是nioEventLoopGroup-2-1,nioEventLoopGroup-2-2,,,nioEventLoopGroup-2-12。

接下来的代码使用for循环,依次创建EventExecutor,可以理解为这就是线程组里面的12个线程,如果创建过程中,发生了异常,则会关闭之前创建好的线程(其实不是很理解netty官方的用意,后面的线程创建失败,跟前面的线程有什么联系吗?)

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;
                        }
                    }
                }
            }
        }
//...
}

上面的构造器,初始化完12个线程后,下面还有一段逻辑,如下所示

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        
//...
        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);
//...

}

chooser是轮到线程池执行线程时,选择哪个线程执行的选择器,它的内部实现有两个选择器,如果线程数量是2的N次方,则使用PowerOfTwoEventExecutorChooser,如果不是,则使用GenericEventExecutorChooser,即通用的选择器,因为我的线程数量是12,不是2的N次方,所以使用了后者。(至于这两个选择器的机制是怎么样的,我在这里就不深入研究了,因为对于一般的简单业务系统而言,只要你保证线程数量够用,业务不会受线程数量,它内部怎么选择线程的,我们可以不用关心)

//...

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

继续看构造器下面的代码,它给每一个线程添加了监听器,监听线程terminate(终结,终止,结束?)的时候采取的措施。terminatedChildren是一个AtomicInteger,我对这段代码的字面理解是,每当有一个线程terminate的时候,这个terminatedChildren就自增1,当它达到和线程组数量一致时,就设置terminateFuture的success为空。。。好吧,我不知道这个后面哪里会用到,因为目前程序跑到这里,线程组只是初始化,还没有真正开始使用。

构造器的最后三行,是把这12个线程组又放进了一个Set集合里面,这个Set集合应该是方便以后使用遍历器Iterator做一些操作而增加的,比如遍历每个线程,然后让它终止terminate。至此,bossGroup的创建就完成了,workerGroup也是一样的流程,就不再赘述了。

3.设置线程组bossGroup和workerGroup

将创建好的两个线程组赋值给ServerBootstrap,其中bossGroup赋值给父类AbstractBootstrap的group,workerGroup赋值给ServerBootstrap的childGroup,都是比较简单的操作,就不贴代码了

4.设置通道类型

执行到示例代码的.channel(NioServerSocketChannel.class)这一行,我们继续跟进,可以看到它是创建了一个通道工厂,后面系统需要Channel的时候,就使用这个channelFactory工厂生产出一个NioServerSocketChannel


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

至于这个工厂是如何生产的,我们可以进到ReflectiveChannelFactory里面,其实在进去之前,就可以根据Reflective这个单词知道,内部肯定是通过Java的反射机制实现的工厂,我们进去看看代码。

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

}

果然不出所料,工厂把NioServerSocketChannel的构造器保存了下来,生产时调用newChannel方法,该方法直接调用保存下来的构造器新建一个实例,然后返回给消费者。

5.设置Handler

//...

                            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                                @Override
                                protected void initChannel(NioSocketChannel ch) throws 
                      Exception {
//                                    ch.pipeline().addLast(new StringDecoder());
//                                    ch.pipeline().addLast(new ServerHandler());
                                    ch.pipeline().addLast(new WriteHandler1());
                                    ch.pipeline().addLast(new WriteHandler2());
                                    ch.pipeline().addLast(new ReadHandler1());
                                    ch.pipeline().addLast(new ReadHandler2());
                                }
                            })

//...

回到开头的示例代码,childHandler方法传入的ChannelInitializer,它的父类是ChannelInboudHandlerAdapter,至于下方的initChannel方法,调试时没有执行,我猜测是后面Channel被激活后(比如客户端创建连接后)才会调用执行Channel的初始化。

6.设置选项option和childOption

SO_BACKLOG这个参数,从字面上很难理解它的用意,于是百度了一下,它是创建连接时的最大等待数量,比如某个系统高并发的情况很频繁,某一个时刻同时有100个连接请求发送过来,而CPU一次只能处理一个连接(不知道我的六核CPU是不是可以同时处理6个连接请求呢?)那么,这100个请求,当前时刻只有1个在处理,其他99个都在排队等待着。那如果是200个连接请求同时到来,那么只有前128个请求会进入等待队列里,后面来的只能丢弃了。

注意:有的读者可能理解成,设置了这个SO_BACKLOG=128后,我的系统只能允许创建128个连接了,不是这样的,我们前面提到的是“同时”。我举一个比较形象的例子,某一家银行的某个营业厅(服务器),它里面有一个柜台小姐姐(CPU),营业厅9点开门后,一下子来了200个人要来取钱或者存钱(请求连接),但是营业厅太小了,只能容纳128个人排队等候,于是保安就在门口拦住了后面想要进来的人,但是只要柜台里处理完了一个业务,下一个人就会去柜台办理,等候的128个位置就会空出一个,如果这时外面有人问可以进去了吗(请求连接),保安看到有空位就会让他进去等候。所以,只要营业厅还没下班(服务器没有停止服务),小姐姐就会一直办理业务,开门时同时进来的128个人办理完后,后面陆续来的人都可以办理业务,如果小姐姐业务能力强,速度快(CPU性能好),一天可以办理一万个业务都有可能。

//...
                            .option(ChannelOption.SO_BACKLOG, 128)
                            .childOption(ChannelOption.SO_KEEPALIVE, true);
//...

SO_KEEPALIVE,即字面理解的意思,保持存活状态,设置为true后,服务器会定时检测TCP连接是否正常,百度了一下说默认是2个小时,如果客户端有回应,则在下一个2小时后继续检测,当然是指该连接没有任何数据交互的前提,只要有数据交互,服务器的定时器就会清零,重新定时2小时(我个人觉得这个KEEPALIVE就很鸡肋了,2个小时才检测一次,如果服务器本来就资源紧缺,然后有大量的连接获取了数据后不主动断开,造成CPU和内存的满负荷,后面的连接都无法进来了。还是拿上面银行的例子举例,大家办理了业务后,都不离开营业厅,全部挤在里面,后面的人都没法进来办理业务,这对于银行和没有办理到业务的人都是一种损失。所以我在实际的项目应用中都是在业务层自己定义心跳包,比如5秒或者10秒一次,超过两三个心跳包周期都没有收到心跳包,则认为客户端离线了,主动把这个TCP连接断开、清除,回收系统资源)

7.绑定端口,启动服务

回到示例代码,bootstrap.bind(5000),5000是本地绑定的端口,我们跳过一小部分无关紧要的代码,来到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;
        }
    }

上面代码的主要逻辑是,首先初始化和注册一个Channel,前面设置过通道类型为NioServerSocketChannel,所以这里肯定也是会初始化成NioServerSocketChannel类型,注册的意思是,将这个Channel注册到监听线程组,告诉线程组,我需要你帮我监听连接和读写事件,当事件来了,你就告诉我(调用相关的回调方法,最后会调用到ChannelHandler的active, inactive, read, readComplete等回调方法里面)

下一步判断初始化和注册是否已完成(初始化和注册是异步的),如果完成了,执行绑定端口操作,如果没完成,则给初始化注册的操作添加监听事件,监听它什么时候完成了再执行绑定端口的操作。

我们再深入到初始化和注册的方法initAndRegister

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            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;
    }

方法里面通过channelFactory工厂生产了一个新的NioServerSocketChannel,然后执行init(channel),如果初始化报错了,则关掉这个Channel,然后返回这个报错结果。如果没有报错,则会执行register注册,可以看到是group()调用的注册,所以就是将该Channel注册到group线程组里(应该是bossGroup,后面调试跟进一下)如果注册也失败了,同样也要关掉这个Channel

我们继续深入init(channel)方法里面

void init(Channel channel) {
        setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
        setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions =
                childOptions.entrySet().toArray(newOptionArray(0));
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                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));
                    }
                });
            }
        });
    }

前两行就是把示例代码设置的option和attrs(示例代码没有设置attrs)赋值给该Channel,然后获取该Channel的管道pipeline,给这个管道添加了一个ChannelInitializer,本质是一个ChannelInboundHandlerAdapter,这个ChannelInitializer里面的initChannel也还没有执行,肯定也是后面线程组会执行到的。

init(channel)执行完后,又回到initAndRegister方法,继续执行下面的register注册操作

final ChannelFuture initAndRegister() {
        //...initChannel部分

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


        return regFuture;
    }

调用config().group()获取到了parentGroup,也就是我们示例代码传进来的bossGroup,然后在这个线程组进行Channel的register注册,进到register方法

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

这里的next是取线程组里的下一个线程来处理register操作,怎么取下一个线程,这个在前面初始化bossGroup时提到有一个线程池选择器,我们这里不关心它是怎么取下一个线程的,我们只关心它取到线程后,是如何进行注册的。继续跟进,我跳过了一些对于主要流程影响不大的代码(解读起来比较花时间,而且跳过也不影响Channel注册的代码),然后来到了AbstractChannel的register方法,如下所示

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

方法的前面是判断执行线程是否为空,是否已经注册过了,线程eventLoop是不是NioEventLoop(这个应该是初始化时设定好的,前面跳过了比较多的细节代码)。判断都没有问题后,执行到if(eventLoop.inEventLoop()),判断传进来的线程是否与当前对象保存的线程一致,因为bossGroup在这之前一直都没有启动过线程,所以这里对象保存的线程是null,不一致,所以执行了else的逻辑,else里面是调用了execute方法,即该线程启动执行,然后执行register0(promise)方法,我们在该方法里面打断点,然后运行程序,可以看到进入该方法后,线程切换到nioEventLoopGroup-2-1了,之前是netty-server-thread,即示例代码Server启动的线程。

进入到register0方法

private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

 判断还没注册过之后,继续进入到doRegister方法

protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

进入这个方法,可以看到是一个死循环,当然里面其实是有return的,就是注册完成后,或者异常抛错退出循环,这里的javaChannel(),实际就是返回Java原生的NIO包下的Channel了(补充知识:netty的NIO其实就是基于Java原生的NIO改造的),继续执行下一步发现直接到return了,是java包里面的方法没法进入吗?总之呢,执行了这一步,就是注册成功了。

我们继续回到register0方法,执行下面的步骤,pipeline.invokeHandlerAddedIfNeeded()

final void invokeHandlerAddedIfNeeded() {
        assert channel.eventLoop().inEventLoop();
        if (firstRegistration) {
            firstRegistration = false;
            // We are now registered to the EventLoop. It's time to call the callbacks for the ChannelHandlers,
            // that were added before the registration was done.
            callHandlerAddedForAllHandlers();
        }
    }

因为是第一次注册,所以会进入到判断里面,然后执行callHandlerAddedForAllHandlers()方法

private void callHandlerAddedForAllHandlers() {
        final PendingHandlerCallback pendingHandlerCallbackHead;
        synchronized (this) {
            assert !registered;

            // This Channel itself was registered.
            registered = true;

            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            // Null out so it can be GC'ed.
            this.pendingHandlerCallbackHead = null;
        }

        // This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
        // holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
        // the EventLoop.
        PendingHandlerCallback task = pendingHandlerCallbackHead;
        while (task != null) {
            task.execute();
            task = task.next;
        }
    }

执行到while循环内部时,task.execute执行发现到了init(channel)时,往channel的管道pipeline增加ChannelHandler的initChannel方法,验证了之前我猜测它会在bossGroup线程启动时执行是对的

 这里获取ServerBootstrap的handler,发现是空的,继续执行下面的操作

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                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));
                    }
                });
            }
        });

channel调用eventLoop执行了一个往管道增加一个ServerBootstrapAcceptor的操作,这个ServerBootstrapAcceptor我们暂且先不看,我们把callHandlerAddedForAllHandlers方法的while循环执行完,后面再重新断点进来看看里面做了什么操作。while的下一次循环,发现task为null,没有task需要执行了,然后又回到了register0方法,执行到pipeline.fireChannelRegistered,对Netty有一定了解的读者应该都知道,fireXXX方法,一般都是在管道中用来调用下一个ChannelHandler的回调方法的,我们继续执行

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

这里看到是调用了head的注册方法,head和tail是两个ChannelHandler,在初始化Channel的Pipeline时默认生成的,进入到invokeChannelRegistered(head)后

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();
                }
            });
        }
    }
    private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

这里的channelRegistered()方法就很熟悉了,就是ChannelHandler里面的回调方法

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

这个方法内部又会继续的fireChannelRegistered,调用下一个ChannelHandler的回调方法,因为head的下一个是tail,所以又调用了tail的回调,我们执行tail的回调,发现tail的回调方法没有做任何操作

 执行完后,我们又再次回到了register0方法,下一步判断是否active,判断是false,因为还没有客户端连接过来,所以这里register0方法就退出了。退出后,该线程任务就跑完了

继续执行跟进后,发现该线程还有任务队列没有完成,就是前面init(channel)方法时,往管道增加ServerBootstrapAcceptor的任务,在此时执行了,一顿跟踪后,终于发现了管道之所以叫管道的精髓,因为管道里面的ChannelHandler都是互相连接的,他们之间互相引用,也就是链表式的结构,而且添加Handler都是添加到tail之前(tail,尾巴的意思)

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

这个ServerBootstrapAcceptor添加到管道后,后面没有什么实际的操作了,我们把该次任务跑完,然后发现任务队列里还有任务,再跟进,我们发现这次的任务是doBind0里的一个线程任务,就是在执行initAndRegister之后,调用了doBind0

    private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

跟进channel.bind方法,后面发现是使用了tail这个ChannelHandlerContext进行绑定的

    @Override
    public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return tail.bind(localAddress, promise);
    }

在它的方法内部,又是找到了标识过绑定事件(MASK_BIND)的OutboundHandler来进行bind

    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }

        final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

调试发现,这个带有MASK_BIND的HandlerContext,原来是head(HeadContext),跟进发现最终的bind操作,也是调用了Java原生的Channel进行bind

    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

bind之后,线程又执行完了,再次找到了下一个任务就是fireChannelActive,很明显又是通知管道中的各个ChannelHandler,回调channelActive方法,从head开始,一直传递到tail,在head的channelActive回调方法中,使能了监听读的事件,readIfIsAutoRead(),因为之前有设置过autoRead=true,一时没想起来在哪一步了。。

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

            readIfIsAutoRead();
        }

active事件执行完之后,线程任务队列里就没有任务了,该线程退出,程序回到了bossGroup的  for主循环里,在这里不停地监听Selector是否有触发连接或者读或者写事件,如果有,就会调用不同的fireXXX方法,触发对这个事件感兴趣的ChannelHandler的回调方法。

到这一步就算是netty服务启动完成了。

8.优雅地关闭netty服务

前面netty服务启动完了,示例代码的线程在bind后阻塞了,那要如何正常地退出netty服务呢?我们只要对示例代码的bind那一行稍作改动,如下所示


                    ChannelFuture channelFuture = bootstrap.bind(port).sync();
                    //将绑定的通道Channel保存下来,想要停止服务时,调用该通道的close,下一行阻塞        
                    //的方法就会往下执行到finally
                    serverChannel = channelFuture.channel();
                    serverChannel.closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (bossGroup != null) {
                        bossGroup.shutdownGracefully();
                    }
                    if (workerGroup != null) {
                        workerGroup.shutdownGracefully();
                    }
                }

bind之后,阻塞直到绑定完成,返回channelFuture,然后从channelFuture中得到channel,这是一个ServerSocketChannel,即服务端的通道,我们只要在退出的时候调用serverChannel.close(),netty框架便会有计划地释放所有的Channel通道,包括用于客户端连接的通道,以及通道里的ChannelHandler

netty内部是如何具体实现正常关闭服务的,这里就不细说了,感兴趣的读者可以自己调试跟踪源码的实现细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值