Netty啓動流程總結

1、什麼是netty

   Netty,一個異步事件驅動的網絡應用框架,用於快速開發可維護的高性能服務器和客戶端。(對java的NIO進行封裝,簡化開發)

1.1 java Nio

BIO模型,一個client線程請求服務端之後,服務端accept請求,並新建線程來處理請求。當併發量變大,服務端創建線程數量過多,線程上下午切換等問題造成性能下降甚至宕機,同時,如果服務端發出讀事件的動作,若數據還未準備好,就會阻塞當前線程。  
NIO模型,un-blocking-io,採用非阻塞方式來讀取客戶端傳給服務端的數據。進行了IO複用與非阻塞模式讀取數據。
IO多路複用(又稱事件驅動):一個線程,監控多個socket的讀寫就緒狀況,這樣,多個描述符的IO操作可以在一個線程內併發交替的順序的完成,這就叫IO多路複用。映射在NIO中即Selector的監聽。
非阻塞模式讀取數據:線程進行讀取數據,若數據爲準備好,線程立馬返回,不會阻塞當前線程。

1.2 NIO中的組件

1、channel(管道)
主要做數據的寫入與讀出操作。
2、Buffer(緩衝)
從channel中讀出的數據與寫入到channel中的數據,都是存放在Buffer中。Buffer有三個屬性,容量(capacity),位置(position),長度限制(limit)。寫模式下,limit-position,表示還有多少數據可以寫入。
3、Selector,即IO多路複用的線程。

1.3 NIO初始化流程

1、初始化channel。
2、初始化Selector並把channel註冊到Selector中。
3、輪訓Selector返回的SelectKeys,找出就緒事件做對應的處理。

2、netty啓動流程分析

客戶端代碼:

public final class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.git
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

服務端代碼:

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        SingleThreadEventLoop singleThreadEventLoop;
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

啓動流程簡述:

  • 服務端先創建兩個線程池組,分別爲BossGroup與WorkerGroup,BossGroup用來監聽客戶端的連接,並把已連接的客戶端分配給WorkerGroup線程池組做處理。初始化好兩個線程池組之後,將兩個線程池組存放在ServerBootStrap中,並設置服務端監聽類NioServerSocketChannel。爲該類添加Handler,本例中即爲LoggingHandler。給WorkerGroup線程組中管理的SocketChannel線程添加自定義的handler類,通過childHandler()方法加入。所有準備工作做好之後,通過bind()方法啓動服務器監聽工作。
  • 客戶端創建一個線程組,並設置到一個新建的BootStrap中,設置客戶端連接類NioSocketChannel,併爲該類設置自定義的handler,通過Connect來啓動連接。

2.1 客戶端啓動流程詳解

1、創建NioEventLoopGroup。

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

判斷傳入的線程數是否爲0,如果不爲0,則使用我們傳入的值,如果未0,則設置爲操作系統內核數*2。

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

最終的構造方法爲MultithreadEventExecutorGroup()

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        //初始化Executor
        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        //創建nThreads個線程池(單例線程池)數組
        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) {
     			。。。。
            } finally {
         
        }
        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);
    }

在這個構造方法中,主要做了一下四步操作。
(1)、初始化executor
(2)、初始化children屬性指向的數組
(3)、通過children數組創建chooser,判斷數組長度是不是2的指數倍數,創建不同的實例。
(4)、爲每一個children數組中的實例設置監聽,並把children數組中的元素加入到set中。

2、接着創建Bootstrap對象,並設置group與channel等參數。

 Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();

3、之後通過connect方法讓客戶端啓動。也是整個netty客戶端的入口。
connect->doResolveAndConnect方法,在這個方法下,主要做兩個操作。
1、initAndRegister()。初始化NIOSocketChannel對象,並初始化,在註冊
2、doResolveAndConnect0()。做實際的連接操作。
進入到initAndRegister()方法中。
通過以下代碼,創建NIOSocketChannel對象。

channel = channelFactory.newChannel();

而這個Factory在bootstrap.channel(NIOSocketChannel)這個方法被調用的時候進行創建。
當創建NIOSocketChannel對象時,進入該對象的構造方法。

public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

通過provider.openSocketChannel()方法,創建了一個SocketChannel對象。

public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }
.......
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

進入到實際的構造方法AbstractNioChannel()中。
在這,設置this.ch = ch,即保存我們創建的SocketChannel。
設置此Channel的模式爲非阻塞,並且設置讀事件。

   protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        //parent:null ch:serverSocketChannel,readInterestOp:accept
        super(parent);
        this.ch = ch;
        //accept事件
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
    }

調用super方法。

protected AbstractChannel(Channel parent) {
        //parent:null
        this.parent = parent;
        // id 是通過算法生成唯一ID,ChannelId
        id = newId();
        //NIO 的 message 的操作類,是 Netty 的核心組件,unsafe
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

爲每個channel創建爲一Id,unsafe類與pipeline。
unsafe是netty與java NIO進行通信的類。
pipeline是netty核心類,保存我們自定義的handler。內部維持一個雙向鏈表結構。
返回initAndRegister()方法中。調用Init(channel)方法,主要是加入我們自定義的handler到我們創建的pipeline中。

void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        p.addLast(config.handler());
    }

進入到addLast方法中,最終調用DefaultChannelPipeline中的addLast方法。在這個addLast()方法中,將我們的handler包裝成一個DefaultChannelHandlerContext對象。調用addLast0方法做實際的插入。插入之後調用以下方法,進行註冊。

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

最終調用unsafe的register方法。register方法設置好AbstractChannel.this.eventLoop之後,調用register0方法。reigister方法中又主要做doRegister()方法。doRegister()方法調用AbstractNioChannel的doRegister()方法。在內部通過for循環註冊,把當前的SocketChannel註冊到線程組eventLoop的其中一個線程上,每個線程組相當於一個Selector。完成實際的註冊工作。完成註冊工作之後,調用pipeline.fireChannelRegistered()方法。
此方法重head結點開始,通過next方法向下遍歷,找到下一個inbound結點,執行該節點的channelRegistered方法。在調用這個方法時,會調用到ChannelInitializer的之channelRegistered()方法,這個方法會把我們自定義的handler加入到pipeline中,加入自定義的handler之後,又會刪除ChannelInitializer對象。代碼如下:

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (initChannel(ctx)) {
            ctx.pipeline().fireChannelRegistered();
        } else {
            ctx.fireChannelRegistered();
        }
    }
 private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                remove(ctx);
            }
            return true;
        }
        return false;
    }

之後,initAndRegister()方法執行完成。調用connect方法做實際的連接服務端工作。而最終又會調用head的connect方法,head的connect方法調用unsafe的connect方法完成連接。

2.2 服務端啓動流程詳解

  • 看完客戶端的啓動過程,再來看服務端的啓動流程。
    服務端新建兩個線程組,分爲BoosGroup和WorkerGroup,創建引導類ServerBootstap,並將兩個線程組放入到引導類中,同時,創建boosGroup的handler以及workerGroup的childHanler,設置好後,通過bind方法啓動流程。
    同樣的,EventLoopGroup的初始化操作也NioServerSocketChannel的初始化同客戶端的類似,就不做分析,我們依然從bind方法開始,bind()方法最終調用的事doBind()方法,doBind()方法中,主要有兩步。
    1、initAndRegister()。
    2、doBind0()。
    進入initAndRegister的init方法中,該方法被ServerBootstrap重寫。在這個方法中,添加了ChannelInitializer代表的handler。進入addLast方法,包裝handler成context對象,加入到pipeline中。返回,進入到register方法中。調用register0()方法,register0調用doRegister()方法。doRegister方法從bossGroup線程組中選中一個線程池作爲Selector,將當前線程註冊到Selector中,註冊之後,調用pipeline.fireChannelRegistered();該方法從head結點開始,找到下一個inBound結點,執行結點的channelRegistered()方法,調用過程中,進入ChannelInitializer的initChannel()方法中,在該方法中,調用initChannel方法,該方法會加入一個ServerBootstrapAcceptor對象,加入完成之後,會刪除ChannelInitializer這個handler結點。之後,繼續向下調用,直到調用到tail結點的channelRegistered方法。而在ServerBootstrapAcceptor對象中,又一個channelRead方法,該方法中加入了我們自定義的Handler加入到pipeline中,同時,將此SocketChannel與workerGroup綁定在一起。那麼這個channelRead()方法何時被調用的呢?當一個client連接到服務端的時候,NioServerSocketChannel會有一個SelectionKey.OP_ACCEPT 就緒事件,接着會調用NioServerSocketChannel.doReadMessages方法,而這個方法中會創建多個NIOSocketChannel對象,同時傳入對象(this),由此我們知道創建這個NioSocketChannel的父channel就是NioServerSocketChannel實例。接下來,Netty的pipeline機制會把讀事件發送到各個handler中,於是就觸發我們前面提到的ServerBootstrapAcceptor.channelRead方法了,所以我們的處理請求的操作也已經分配給worker線程來處理了。由此,init方法也執行完成,執行完成之後,調用doBind0()方法,而最終會又tail結點觸發,調用到head結點,處理bind,head調用unsafe做實際的bind操作。
    服務端就不做代碼分析,純粹總結。
    服務端關鍵的地方在於,init方法,加入了ServerBootStrapAcceptor對象,該對象的channelRead爲每一個新建的SocketChannel對象分配pipeline。

3 梳理請求流程

EventLoopGroup的每個實例爲SingleThreadEventExecutor,而這個類中有一個Thread屬性,而這個屬性的作用就是讓調用NioEventLoop.run(),這個Thread屬性代表的線程在register0方法被執行的時候啓動,即eventLoop.execute()提交一個任務的時候,就會讓這個線程啓動。繼續跟蹤NioEventLoop.run() 方法

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

通過for循環判斷當前是否還有task任務,若有任務調用selectNow()方法,否則,調用select(oldWakenUp) 方法。即若有任務,則立馬返回,執行task,否則阻塞等待。當IO事件返回時,就處理IO事件,分別是processSelectedKeys()和runAllTasks();方法,第一個方法找到IO事件並處理,第二個方法是運行taskQueue任務。而processSelectedKey 方法底層就是調用unsafe類的read,write和connect事件,而unsafe處理這些事件即調用pipeline中的handler做對應處理。處理完後,如果任務隊列中還有任務,則取出任務做執行。
到此,netty的原碼暫時告一段落。

4 總結

1、初始化線程池組。
2、初始化引導類。
3、調用bind或connect方法做服務觸發。
4、initAndRegister方法中,通過工程產生對應的NioServerSocketChannel或NioSocketChannel,並初始化,構建unsafe和pipeline。
5、調用init方法初始化,添加默認的handler,ChannelInitializer,然後ChannelInitializer的方法來添加自定義的handler。服務端通過ServerBootstrapAcceptor添加自定義的handler,並把SocketChannel與客戶端綁定。
6、調用Register方法,做註冊,最終調用register0。
7、通過doRegister()方法,將當前SocketChannel與線程組中的Selector綁定。
8、調用pipeline.fireChannelRegistered()方法,將自定義handler註冊到pipeline中,workergroup與SocketChannel做綁定。
9、調用doBind方法,做實際綁定。
10、調用結束。
當一個client連接到server時,server產生一個accept事件,調用doReadMessages創建NioSocketChannel對象,當client傳來數據是,服務端的NioEventLoop會針對client不同的事件,調用unsafe中處理對應事件的方法。而unsafe通過調用pipeline來做對應方法的執行。而對於pipeline中的handler來說,outbound事件都是請求事件。即某件事情發生,outbound做事件通知,從tail到head.

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章