Simple analysis of Netty server startup process source code

Posted by barffyman on Wed, 19 Jan 2022 00:09:53 +0100

Introduction:

Netty is a client-side and server-side programming framework based on NIO. It provides asynchronous and event driven network application framework and tools to quickly develop high-performance and reliable network server and client programs

analysis:

package com.yxj.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup parentGroup = new NioEventLoopGroup();//The main eventLoopGroup is responsible for handling the connection events requested and forwarding them to the working eventLoopGroup
        EventLoopGroup childGroup = new NioEventLoopGroup();//The working eventLoopGroup is responsible for handling the read and write events in the request.
        try {

            ServerBootstrap bootstrap = new ServerBootstrap();//Server startup class, corresponding client startup class: Bootstrap
            bootstrap.group(parentGroup, childGroup)//Set two eventloopgroups, the main setting is in AbstractBootstrap, and the working setting is in ServerBootstrap itself
                    .channel(NioServerSocketChannel.class)//Set the channel type of the server to NioServerSocketChannel. The corresponding client is NioSocketChannel
                    //. handler(null) / / set the handler of the main eventLoopGroup. This is an example. If the actual parameter is set to null, an error will be reported: handler(null)
                    .childHandler(new ChannelInitializer<SocketChannel>() {//Set the handler for the working eventLoopGroup
//netty is based on the master-slave Reactor model, so the childGroup is actually responsible for reading and writing, so the handler of eventLoopGroup is set here.
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());//ChannelInboundHandler stack type processing: String decoder
                            pipeline.addLast(new StringEncoder());//ChannelOutboundHandler stack type processing: String encoder
                            pipeline.addLast(new NettyServerHandler());//Custom ChannelInboundHandler type processor: server processing logic
                        }
                    });

            ChannelFuture future = bootstrap.bind(8888).sync();//The core method is bind. Returns an asynchronous operation result ChannelFuture 
            System.out.println("Server started...");

            future.channel().closeFuture().sync();//Monitor the closing status, and the closing method will be executed automatically. ChannelFuture asynchronous feature
        } finally {
            //Elegant close
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

The above is a simple Netty server startup demo example, in which the core server startup process is encapsulated in the bind method. Click to view it and find a doBind method:

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();//Complete initialization and registration. The core part of the startup process is here
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        } else if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);//Complete the operation of binding the local port (you can see that after registration, bind the network port number)
            return promise;
        } else {
            final AbstractBootstrap.PendingRegistrationPromise promise = new AbstractBootstrap.PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {//(as you can see, register first and then bind the network port number)
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);//Complete the operation of local binding port
                    }

                }
            });
            return promise;
        }
    }

The dobind method is in the parent class AbstractBootstrap of ServerBootstrap. The method has two important invocations. initAndRegister() and doBind0(), doBind0() finally call java's native method to bind the port number and start network monitoring. The core process of starting the Netty service is to look at the initAndRegister() method

initAndRegister()

 final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            //Create a default NioServersocketChannel instance through reflection
            channel = this.channelFactory.newChannel();//Why nioserver socketchannel? You can click in and follow me
            this.init(channel);//Initialize the NioServersocketChannel. This method is an abstract method implemented by ServerBootstrap itself
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);//Register this nioserver socketchannel somewhere?
        //Do you remember that NIO channel s can only be used and monitored after they are registered with the Selector! So here is the core registration step.
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

Focus on analyzing the source code. The initAndRegister method consists of init and register, so you only need to look at the init and register methods

init()

void init(Channel channel) {
        setChannelOptions(channel, this.newOptionsArray(), logger);//Set Options for NioServersocketChannel (not important)
        setAttributes(channel, this.newAttributesArray());//Set the Attributes of NioServersocketChannel (not the key)
        ChannelPipeline p = channel.pipeline();
        final EventLoopGroup currentChildGroup = this.childGroup;//childGroup is the serverbootstrap in the server startup demo The working eventLoopGroup set in the group () method
        final ChannelHandler currentChildHandler = this.childHandler;//childHandler is the serverbootstrap in the server startup demo The event handler set in the childHandler () method
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(this.childOptions);//The server starts ChildOptions set by ServerBootstrap in the demo (the above example demo is not written)
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(this.childAttrs);//The server starts ChildAttrs set by ServerBootstrap in the demo (the above example demo is not written)
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {//Set channelHandler for nioserver socketchannel.
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();//The channelHandler here is serverbootstrap in the server startup demo Handler (channelHandler channelhandler) method.
                //(the demo of the above example is not written out. The handler() method is to set the event handler for the main eventLoopGroup. The childHandler() method sets the event handler() for the working eventLoopGroup
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {//Not the point
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }

The init method is summarized as follows: complete the initialization of Options, Attributes and handler in nioserver socketchannel.

register()


In fact, the final call is not in the SingleThreadEventLoopGroup. And the next floor

    public ChannelFuture register(Channel channel) {
        return this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));
    }

    public ChannelFuture register(ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        //Here, the register method of AbstractUnsafe will be called to complete the registration
        promise.channel().unsafe().register(this, promise);//AbstractUnsafe is an internal class of AbstractChannel
        return promise;
    }

Continue to the register method of AbstractUnsafe

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");
            if (AbstractChannel.this.isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
            } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
                promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
            } else {
                AbstractChannel.this.eventLoop = eventLoop;
                if (eventLoop.inEventLoop()) {//Judge that the current thread for register method is the currently selected eventLoop.
                //Multithreadeventloopgroup Register method, which randomly selects an eventLoop in the main eventLoopGroup to bind with nioserver socketchannel.
                //A NioServersocketChannel corresponds to an eventLoop. One eventLoop can correspond to multiple nioserver socketchannels
                    this.register0(promise);//Core method
                } else {
                //If the current thread is not the selected eventLoop, the registration task is encapsulated into a task to be executed by the eventLoop
                    try {
                        eventLoop.execute(new Runnable() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);
                            }
                        });
                    } catch (Throwable var4) {
                        AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
                        this.closeForcibly();
                        AbstractChannel.this.closeFuture.setClosed();
                        this.safeSetFailure(promise, var4);
                    }
                }

            }
        }

        private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !this.ensureOpen(promise)) {
                    return;
                }

                boolean firstRegistration = this.neverRegistered;//Whether to register for the first time. The default value is true
                AbstractChannel.this.doRegister();//Method of final registration
                this.neverRegistered = false;//After registration, change the "re unregistered ID to false"
                AbstractChannel.this.registered = true;
                AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
                this.safeSetSuccess(promise);
                AbstractChannel.this.pipeline.fireChannelRegistered();//Execute the ChannelRegistered() method of the channelhandler chain
                if (AbstractChannel.this.isActive()) {
                    if (firstRegistration) {
                        AbstractChannel.this.pipeline.fireChannelActive();//Execute the ChannelActive() method of the channelhandler chain. But I won't go here the first time I'm called. On the first call, the channelactive () method of the channelhandler chain is executed in other methods
                    } else if (AbstractChannel.this.config().isAutoRead()) {
                        this.beginRead();
                    }
                }
            } catch (Throwable var3) {
                this.closeForcibly();
                AbstractChannel.this.closeFuture.setClosed();
                this.safeSetFailure(promise, var3);
            }

        }

Next, let's look at the final registration method in the doRegister() of AbstractNioChannel

protected void doRegister() throws Exception {
        boolean selected = false;

        while(true) {
            try {
            //Bind the current eventLoop with the current NioServerSocketChannel and return an ID selectionKey. Complete registration
            //Netty is a framework encapsulated on nio. nio binds the channel to the selector and starts listening to the channel. Netty binds the channel to the eventLoop to start listening
                this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException var3) {
                if (selected) {
                    throw var3;
                }

                this.eventLoop().selectNow();
                selected = true;
            }
        }
    }

Summary

The Netty service sets a series of necessary parameters into ServerBootstarp through chain calls, and then calls the bind method to complete the initialization and registration of the configuration, as well as the binding of the network port.

Two important methods are called in the bind method: initAndRegister() and doBind0()

initAndRegister(): create a NioServerSocketChannel with default configuration, and then initialize various configurations: main eventGroup, working eventloopgroup, event handler, etc. Finally, bind and register this NioServerSocketChannel with an eventLoop randomly selected from the main eventloopgroup, and return a selectionKey to identify the relationship between them. When listening, you can use this selectionKey to know which channel the event source is.

doBind0(): bind the local network port by calling java's native method to complete the whole service startup process

Topics: Java Netty network