Netty Server 啓動流程

  在分析netty 服務端啓動流程之前,我們先看下 java nio socket服務端的代碼如何編寫,因爲netty nio 框架底層最終還是要用java nio那一套方式啓動服務。

        //創建channel
        ServerSocketChannel channel = ServerSocketChannel.open();
        //綁定端口
        channel.socket().bind(new InetSocketAddress("localhost", 8080));
        //設置成非阻塞
        channel.configureBlocking(false);

        Selector selector = Selector.open();
        //註冊到selector 對accept connection 感興趣
        channel.register(selector, SelectionKey.OP_ACCEPT);
        //監聽selector事件,對不同類型的事件做相應的處理
        .....

  下面來看下netty 啓動socket 服務端的代碼(Netty 版本4.1.10-Final)

        //接收連接請求的事件循環
        EventLoopGroup bossGroup =new NioEventLoopGroup();
        //接收IO讀寫的事件循環
        EventLoopGroup workerGroup =new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).
            //指定Channel(netty中的)
            channel(NioServerSocketChannel.class)
            //sockect channel的處理器鏈
            .childHandler(new ServerInitializerXXX());
            
            //綁定端口
            ChannelFuture sync = serverBootstrap.bind(9000).
            sync();
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

代碼中bind方法要傳端口號,說明這是啓動服務端的入口,接下來分析一下bind方法。代碼一路跟蹤下去,走到doBind方法。(分析的代碼中砍掉了一些異常處理流程,和不重要的邏輯)

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        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 裏面的代碼砍掉了
    }

這裏我們可以猜到netty做了2件事情
1.initAndRegister 方法:創建了Channel,並且註冊到selector
2.doBind0 方法:傳入了SocketAddress,說明進行了端口綁定操作

接下來我們看下netty是如何創建channel,註冊到selector的。進入initAndRegister 方法

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //創建Channel對象
            channel = channelFactory.newChannel();
            //初始化channel
            init(channel);
        } catch (Throwable t) {
           //相關代碼砍掉了
        }
        //將Channel對象註冊到selector
        ChannelFuture regFuture = config().group().register(channel);
        //判斷註冊到selector 是否有異常,如果有異常關閉channel
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

initAndRegister 做了三件事情
一.通過 channelFactory 創建了channel對象。此處的channelFactory是在創建了ServerBootstrap對象後調用channel方法時創建.


 public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        //ServerBootstrap中的channelFactory引用ReflectiveChannelFactory對象。
        //ReflectiveChannelFactory 對象中引用了channel方法中傳入的Channel.class,ReflectiveChannelFactory 通過反射的形式創建Channel對象
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }
  // 小插曲
//這裏創建channel對象使用SelectorProvider.openServerSocketChannel() 的方式創建,使用原生的ServerSocketChannel.open()創建channel,會加鎖有性能損失.(https://github.com/netty/netty/issues/2308)

二.通過init方法初始化channel

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.設置attribute
2.設置ChannelOption
3.註冊了一個ChannelHandler,在channel pipleline中出入了一個 ServerBootstrapAcceptor對象。ServerBootstrapAcceptor主要用來將新的請求轉發到child eventLoopGroup.
這塊邏輯不是很重要

三.channel 註冊

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

一路跟蹤到AbstractChannel 的register方法

  //砍掉了細枝末節的邏輯
 public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            AbstractChannel.this.eventLoop = eventLoop;
            //判斷是不是當前線程在做register操作,保證一個線程負責一個channel的整個生命週期,避免線程安全問題
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                }
            }
        }

register0方法

private void register0(ChannelPromise promise) {
            try {
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;
                
                pipeline.invokeHandlerAddedIfNeeded();
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
            }
        }

1.doRegister :將channel 註冊到selector
2.invokeHandlerAddedIfNeeded :調用pipeline中channel的handlerAdded方法
3.fireChannelRegistered:調用pipeline中channelRegister方法
4.判斷channel是否是active狀態,如果channel 綁定了端口認爲是active。此時還沒有走到bind端口,不會觸發調用ChannelActive方法。

Channel創建完成,註冊到selector後開始綁定端口。我們回到doBind0方法,代碼跟蹤到AbstractChannelHandlerContext的bind 方法

public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        //在一個線程中執行channel的端口綁定
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

再一路跟蹤到AbstractChannel的bind 方法

          //判斷是否綁定端口
          boolean wasActive = isActive();
            try {
               // 執行ServerSocketChannel的bind方法,綁定端口
                doBind(localAddress);
            } catch (Throwable t) {
            }
            if (!wasActive && isActive()) {
               //異步觸發調用pipeline中channelHandler的ChannelActive方法
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

總結

netty 服務端啓動流程
1.創建channel對象,設置option,attribute,添加ServerBootstrapAcceptor 接收器。
2.channel 註冊到selector
3.觸發channelAdd,channelRegistered
4.bind 端口
5.觸發ChannelActive

參考文章

netty源碼分析之服務端啓動全解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章