Netty源码分析-Server端启动

本文主要对Netty中Server端启动进行分析,分析Server端是如何绑定端口,初始化Selector,启动NioEventLoop,并最终实现Reactor模式的。
首先看一段经典的Server启动代码。可以看到该过程中实例化了两个EventLoopGroup县城组:bossGroup、workerGroup。可以看到这个Server采用的是Reactor的多线程模式。创建的SimpleNettyServerHandler是通过childHandler()方法加入到workerGroup线程去执行的。

    public void bind(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new SimpleNettyServerHandler());
                    }
                });
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

1. bind()入口

下面就以serverBootstrap.bind(port)为入口,进一步分析实际Server的启动流程的,直接进入到doBind(final SocketAddress localAddress)方法:
1.会调用initAndRegister()初始化并注册一个Channel,此处的Channel是一个NioServerSocketChannel,该方法返回一个ChannelFuture。
2.判断ChannelFuture是否已经执行完成,如果执行完成会调用doBind0()方法;如果没有执行完成,也会通过添加监听器等待执行完成后再调用doBind0()方法。
因此,重点就落在initAndRegister()doBind0()这两个方法上来了。

    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) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();

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

1.1 initAndRegister()分析

分析一下initAndRegister()方法。通过函数名也大致可以看出该方法工作分为两部分:init(Channel channel)register

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

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

        // If we are here and the promise is not failed, it's one of the following cases:
        // 1) If we attempted registration from the event loop, the registration has been completed at this point.
        //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
        // 2) If we attempted registration from the other thread, the registration request has been successfully
        //    added to the event loop's task queue for later execution.
        //    i.e. It's safe to attempt bind() or connect() now:
        //         because bind() or connect() will be executed *after* the scheduled registration task is executed
        //         because register(), bind(), and connect() are all bound to the same thread.

        return regFuture;
    }

1.1.1 init过程分析

看一下init(Channel channel)的实现。首先对Channel设置了ChannelOption和AttributeKey,然后会执行最重要的一步:在Channel的pipeline中添加ChannelHandler。具体ChannelPipeline的讲解会在单独的文章,可以看到该ChannelHandler的最终实现是一个ServerBootstrapAcceptor,通过名字就可以看出这是一个专门做Acceptor的handler,具体对它的分析可以放在文章的后边。值得注意的是给pipeline添加ServerBootstrapAcceptor的过程是通过ch.eventLoop().execute(new Runnable()执行的,ch.eventLoop()返回实际就是NioEventLoop(具体可见:),所以实际是通过NioEventLoop中执行非I/O任务执行的。

    @Override
    void init(Channel channel) throws Exception {
        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()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();

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

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

1.1.2 register分析

通过分析上边的init方法,当前的阶段是Channel已经初始化好了,Channel对应的Pipeline也已经初始化完成。但是这个Channel与Reactor线程之间还没有建立联系,下边的register的过程就是将这个Channel在NioEventLoop线程上进行注册。
注册的核心代码在AbstractChannel$AbstractUnsafe.register方法中,下边的这段堆栈信息展示了是如何调到这个方法的:

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:456)
      at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80)
      at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:74)
      at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86)
      at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:332)
      at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:283)
      at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:279)
      at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:254)
      at com.zhangyk.server.SimpleNettyServer.bind(SimpleNettyServer.java:35)
      at com.zhangyk.server.SimpleNettyServer.main(SimpleNettyServer.java:50)

register方法的实现非常简单,会将最终的实际执行封装在方法register0(ChannelPromise promise)中,然后根据线程状态决定是在当前线程执行还是放在ch.eventLoop().execute(new Runnable()执行。
1)执行doRegister(),这是最核心的一步,具体的实现在类io.netty.channel.nio. AbstractNioChannel中,后边有贴源码实现,代码中可以看到会将javaChannel注册到NioEventLoop的selector上,这样NioEventLoop的selector就可以监听到Channel的I/O事件变化。
2)执行Channel对应Pipeline的fireChannelRegistered()方法
3)根据是否是firstRegister以及isAutoRead()决定是否执行Pipeline的fireChannelActive()方法和beginRead()方法

        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) {
              .......
            }
        }
public abstract class AbstractNioChannel extends AbstractChannel {
    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;
                }
            }
        }
    }
}

至此,可以看到,我们已经创建并初始化了NioServerSocketChannel,并且将该Channel注册到了NioEventLoop的Selector上。实际上,NioEventLoop的Loop循环已经可以开始监听ServerChannel的I/O事件了。

1.2 dobind分析

由上所知,initAndRegister()之后,Channel已经初始化好,并且对应的Selector都已经注册监听,NioEventLoop也已经开始Loop循环。现在还需要将该channel与我们所要监听的port端口进行绑定。可以看到,端口绑定的操作也是通过channel.eventLoop().execute 执行的。

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

2. 启动状态图

此处贴用别人blog的一张图来表示一下。启动的操作一部分由Main线程完成,另一部分由bossGroup线程(即NioEventLoop线程)完成。
NioEventLoop线程除了执行I/O任务(第二个),还执行了doRegister()、doBind()、finishPipeline()这三个非I/O任务。

server启动状态图

3. ServerBootstrapAcceptor类分析

通过上边的分析,我们知道ServerBootstrapAcceptor是Acceptor Pipeline中的一个handler。由它负责对NioServerChannel上发生I/O事件进行处理。譬如,当NioServerChannel上有一个新的连接请求时,会由bossGroup线程的I/O任务Selector监听到(具体可参见NioEventLoop中I/O任务的分析过程),并通过调用 processSelectedKeys()方法,并最终执行到ServerBootstrapAcceptorchannelRead(ChannelHandlerContext ctx, Object msg)方法。
分析一下channelRead(ChannelHandlerContext ctx, Object msg)方法,参数里边msg实际会是通过accept与客户端建立的一个子连接child。具体的实行过程:
1.给这个子连接child的pipeline 增加Handler,这个Handler就是文章最开始那段代码中通过childHandler添加的那个Handle。代码及child.pipeline().addLast(childHandler)
2.给子连接设定一些属性childOptions、AttributeKey等
3.将子连接child注册到childGroup中.后续关于该子连接的一些I/O事件及其执行会在childGroup的EventLoop中。

private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {

        private final EventLoopGroup childGroup;
        private final ChannelHandler childHandler;
        private final Entry<ChannelOption<?>, Object>[] childOptions;
        private final Entry<AttributeKey<?>, Object>[] childAttrs;
        private final Runnable enableAutoReadTask;

        ServerBootstrapAcceptor(
                final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
                Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
            this.childGroup = childGroup;
            this.childHandler = childHandler;
            this.childOptions = childOptions;
            this.childAttrs = childAttrs;

            // Task which is scheduled to re-enable auto-read.
            // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
            // not be able to load the class because of the file limit it already reached.
            //
            // See https://github.com/netty/netty/issues/1328
            enableAutoReadTask = new Runnable() {
                @Override
                public void run() {
                    channel.config().setAutoRead(true);
                }
            };
        }

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

        private static void forceClose(Channel child, Throwable t) {
            child.unsafe().closeForcibly();
            logger.warn("Failed to register an accepted channel: {}", child, t);
        }

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

推荐阅读更多精彩内容