2、Netty那些事 - 服务端之ServerBootStrap启动分析

493 阅读5分钟

襄阳好风日,留醉与山翁。大家好,我是Y,上篇文章主要给大家简单介绍了下Netty的一些概念和原理性的东西,今天就带着大家一起过下服务端的启动核心流程。

一、服务端Demo

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap server = new ServerBootstrap()
            .group(bossGroup, workGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ServerChannelInitializer());

        try {
            ChannelFuture future = server.bind(this.port).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());
        // 自己的业务handler
        pipeline.addLast(new MyServerHandler());
    }
}

以上就是Netty server端的简单demo,核心步骤如下:

  • 分别创建了boss线程组和worker线程组。boss线程组一般来说设置一个线程即可,worker线程如果不传任何参数则默认创建的个数为NettyRuntime.availableProcessors() * 2。
  • 初始化NioServerSocketChannel同时会开启selector多路复用器,并将handler添加到NioServerSocketChannel的管道中去。这些handler包含用来处理心跳的IdleStateHandler,用来编解码的StringDecoder/Encoder(同样可以选择其他的编解码器)以及业务自定义handler。
  • 最后调用server.bind方法,绑定对应的端口,等待客户端的请求到来。

这里要注意一点,由于Netty服务启动后会阻塞线程,因此实际应用中一般都是在异步线程中去启动Netty。

二、ServerBootStrap-bind分析

image.png 老规矩,有图当然先看图了,以上就是ServerBootStrap-bind方法时序图。涉及的类比较多,如果看不动的同学可以优先看下最后一个类ServerSocketChannelImpl,熟悉java socket编程的同学可能对这个类很熟悉,该类就是java原生的用来进行网络通信的类。也就是说,bind方法执行到最后,其实是把ServerSocketChannel封装成了NioServerSocketChannel,底层通信还是依赖的java原生类ServerSocketChannel。

2.1. 步骤2-doBind分析

    private ChannelFuture doBind(final SocketAddress localAddress) {
        // 初始化channel并注册到NioEventLoop线程上
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        // 注册是否异常
        if (regFuture.cause() != null) {
            return regFuture;
        }
        if (regFuture.isDone()) {
            // 组册完成后要进行端口绑定
            // 又NioEventLoop线程去异步执行
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // 注册操作是异步的,可能会没完成,先给主线程返回PendingRegistrationPromise
            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();
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

通过上述源码可以看到,doBind首会把channel注册到NioEventLoop上并开启selector,此时还未进行OP_ACCEPT事件绑定。由于注册的过程是由NioEventLoop线程去执行的,是异步的,因此要等注册动作完成后,才能进行端口的绑定。可以看到,做了几次状态的判断,如果注册动作未完成,则会注册一个监听,等注册动作完成后,会调用operationComplete方法,执行后续的端口绑定动作。

2.2. 步骤三-initAndRegister分析

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            // 通过反射创建NioServerSocketChannel实例
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            ... ... 
        }
        // 底层调用的就是AbstractChannel.AbstractUnsafe-register方法
        // 此处不展开,等分析AbstractChannel时候在讲解下调用链路
        // 此处.group获取的是boss group,也就是把serverSocketChannel交给了boss group去处理
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }
    

debug可以看到,此处的channelFactory实际类型为ReflectiveChannelFactory,反射创建的channel对象为 NioServerSocketChannel。 image.png channelFactory类型的确定其实是在new ServerBootstrap() .channel(NioServerSocketChannel.class)时候,查看源码如下:

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

初始化NioServerSocketChannel实例主要为了初始化ServerSocketChannel对象,查看源码如下:

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        return provider.openServerSocketChannel();
    }
    // SelectorProviderImpl-openServerSocketChannel实现
    public ServerSocketChannel openServerSocketChannel() throws IOException {
        return new ServerSocketChannelImpl(this);
    }

初始化channel后,则要进行selector的绑定和注册,通过上面的时序图可以看到,最终调用的是EventLoop的register方法,把channel注册到对应的selector上,使得select可以监听channel的各种事件,EventLoop等后面再分析。

2.3. 步骤七-doBind0分析

通过上面时序图也可以看到,bind的过程涉及了多个类,最终执行的是AbstractChannel的内部类AbstractUnsafe的bind(SocketAddress localAddress, ChannelPromise promise),查看其源码实现如下:

        public final void bind(SocketAddress localAddress, ChannelPromise promise) {
            this.assertEventLoop();
            if (promise.setUncancellable() && this.ensureOpen(promise)) {
                ... ... 
                boolean wasActive = AbstractChannel.this.isActive();

                try {
                    AbstractChannel.this.doBind(localAddress);
                } catch (Throwable var5) {
                    this.safeSetFailure(promise, var5);
                    this.closeIfClosed();
                    return;
                }

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

                this.safeSetSuccess(promise);
            }
        }
  • 调用抽象方法doBind

查看其实现发现,调用的是NioServerSocketChannel-doBind发下,源码如下:

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

最终调用的java nio的ServerSocketChannelImpl bind方法进行端口绑定:

    @Override
    public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
        synchronized (lock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (isBound())
                throw new AlreadyBoundException();
            InetSocketAddress isa = (local == null) ? new InetSocketAddress(0) :
                Net.checkAddress(local);
            SecurityManager sm = System.getSecurityManager();
            if (sm != null)
                sm.checkListen(isa.getPort());
            NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
            Net.bind(fd, isa.getAddress(), isa.getPort());
            Net.listen(fd, backlog < 1 ? 50 : backlog);
            synchronized (stateLock) {
                localAddress = Net.localAddress(fd);
            }
        }
        return this;
    }
  • 调用invokeLater 绑定端口成功后,会触发active事件,调用invokeLater方法,用来给注册到Selector上的socket channel加上OP_ACCEPPT事件。

image.png

invokeLater方法调用链路有点深,可以看到最终调用的是AbstractNioChannel-doBeginRead方法实现事件注册,查看源码如下: image.png 底层其实调用的还是java nio的原生类SelectionKey,这里的readInterestOp=16表示的当前是OP_ACCEPT事件,实现OP_ACCEPT事件注册,具体的事件类型都定义在SelectionKey里

    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;

三、小结

本文主要为大家简单介绍了Netty ServerBootStrap相关源码实现,主要做了几件事

  • 初始化NioServerSocketChannel,并包装jdk nio实现类ServerSocketChannel,将handler添加到NioServerSocketChannel的pipline管道中去。
  • 初始化EventLoop并开启Selector,此时还未注册OP_ACCEPT
  • 调用底层java nio相关接口绑定端口后,会触发active事件,底层通过调用java nio原生类SelectionKeyImpl将事件OP_ACCEPT(具体值为16)注册到对应的selector上。

这里还要分线程讲下,netty启动的过程中主要设计三个线程,分别为主线程、boss线程、worker线程。

  • main线程职责
    • 创建select
    • 创建server socket channel并初始化
    • 从boss group中选择一个NioEventLoop交给server socket channel
  • boss线程职责
    • 将server socket channel注册到选择的NioEventLoop的selector上
    • 绑定地址启动
    • 注册接受连接事件(OP_ACCEPT到selecor上
  • worker线程职责
    • 将socket channel注册到选择的NioEventLoop的selector
    • 注册读事件(OP_READ)到selector上,并处理