“This is the 11th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Introduction to the

While Netty is powerful, building programs using Netty is easy. You only need to master certain Netty routines to write powerful Netty programs. Every Netty program needs a Bootstrap. What is Bootstrap? Bootstrap translates to shoehorn in Chinese. In the computer world, Bootstrap refers to a Bootstrap program that can be easily built and launched.

There are two types of Bootstrap in netty: the client Bootstrap and the ServerBootstrap. What’s the difference? How do these two bootstraps work in Netty? Take a look.

Connection between Bootstrap and ServerBootstrap

First, take a look at the inheritance relationship between Bootstrap and ServerBootstrap, as shown below:

Bootstrap and ServerBootstrap both inherit from AbstractBootstrap, and AbstractBootstrap implements the Cloneable interface.

AbstractBootstrap

There is also a Channel in the figure above. How does a Channel relate to AbstractBootstrap?

Let’s look at the definition of AbstractBootstrap:

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
Copy the code

AbstractBootstrap accepts two generic arguments, one that B inherits from AbstractBootstrap and one that C inherits from Channel.

Let’s take a look at what a simple Bootstrap startup requires:

EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FirstServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(port).sync(); // Bind the port and start receiving connections. F.channel ().closeFuture().sync();Copy the code

The above code is one of the most basic and most standard netty server startup code. You can see that the elements associated with Bootstrap are as follows:

  1. EventLoopGroup, used for channel registration and traversal.
  2. Channel or ChannelFactory, which specifies the type of channel to use in Bootstrap.
  3. ChannelHandler, which specifies the processing logic for messages in a specific channel.
  4. ChannelOptions: indicates the properties of a channel.
  5. SocketAddress,bootstrap boot is bound IP and port information.

So far, Bootstrap is related to these five values, and AbstractBootstrap’s constructor defines the assignment of these properties:

    AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
        group = bootstrap.group;
        channelFactory = bootstrap.channelFactory;
        handler = bootstrap.handler;
        localAddress = bootstrap.localAddress;
        synchronized (bootstrap.options) {
            options.putAll(bootstrap.options);
        }
        attrs.putAll(bootstrap.attrs);
    }
Copy the code

The group, channel,option, and other methods in the sample code actually assign values to these properties without doing much business.

Note that there is only one group property in AbstractBootstrap, so the two group properties are extension properties added in ServerBootstrap.

In Bootstrap, there are actually two methods for assigning a channel. One is to pass in the channel directly, and the other is to pass in the ChannelFactory. ChannelFactory = ChannelFactory = ChannelFactory = ChannelFactory

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

ChannelClass is encapsulated in a ReflectiveChannelFactory, and ultimately the channelFactory property is set.

The way to actually start a service in AbstractBootstrap is bind, which passes in a SocketAddress and returns a ChannelFuture, which obviously creates a channel. Let’s look at an implementation of the bind method:

   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) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
Copy the code

In the doBind method, the initAndRegister method is first called to initialize and register a channel.

Channels are created using the newChannel method of the channelFactory:

channel = channelFactory.newChannel();
Copy the code

The init method that initializes the channel is then called. This init method is not implemented in AbstractBootstrap and needs to be implemented in a concrete implementation class.

Once you have a channel, you register it with the EventLoop by calling the Register method of the EventLoopGroup and return the ChannelFuture generated by the registration.

If the channel is successfully registered, the doBind0 method is called to complete the final binding work:

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

Since eventLoop is itself an Executor, it can execute a specific command. In its execute method, it passes in a new Runnable object that executes channel.bind in its run method. Bind a channel to a SocketAddress.

At this point, the bind method of Bootstrap completes.

Let’s review the basic flow of bind again:

  1. Create a channel with the ChannelFactory.
  2. Register a channel with the EventLoopGroup in Bootstrap.
  3. If the channel is registered successfully, the Execute method of EventLoopGroup is called to bind the channel to SocketAddress.

Is it clear?

With AbstractBootstrap out of the way, let’s move on to Bootstrap and ServerBootstrap.

The Bootstrap and ServerBootstrap

Bootstrap is mainly used by clients or UDP protocols.

Let’s look at the definition of Bootstrap:

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> 
Copy the code

Bootstrap has one more property and one more method than AbstractBootstrap.

One more attribute is resolver:

private static final AddressResolverGroup<? > DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE; private volatile AddressResolverGroup<SocketAddress> resolver = (AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;Copy the code

AddressResolverGroup contains an IdentityHashMap whose key is EventExecutor and value is AddressResolver:

    private final Map<EventExecutor, AddressResolver<T>> resolvers =
            new IdentityHashMap<EventExecutor, AddressResolver<T>>();
Copy the code

The AddressResolverGroup actually maintains a mapping between EventExecutor and AddressResolver.

AddressResolver is used to resolve the address of the remote SocketAddress. Because the remote SocketAddress may not be an IP address, use AddressResolver to resolve it.

The EventExecutor here is actually the EventLoop registered by the channel.

In addition, Bootstrap, as a client application, needs to connect to the server, so the Bootstrap class has a method to connect to the remote SocketAddress:

    public ChannelFuture connect(SocketAddress remoteAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }
Copy the code

The connect method has similar logic to the bind method, with the addition of a resolver’s resolve procedure.

After parsing, the doConnect method is called to make the actual connection:

private static void doConnect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. final Channel channel = connectPromise.channel(); channel.eventLoop().execute(new Runnable() { @Override public void run() { if (localAddress == null) { channel.connect(remoteAddress, connectPromise); } else { channel.connect(remoteAddress, localAddress, connectPromise); } connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); }}); }Copy the code

As you can see, the doConnect method is similar to the doBind method in that it executes the connect or bind method of the channel through the eventLoop registered with the current channel.

Take a look at the ServerBootstrap definition again:

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
Copy the code

ServerBootstrap is used on the server, so Bootstrap is not used to resolve SocketAddress, so there is no resolver attribute.

On the server side, however, you can use the Parent EventLoopGroup to accept connections, and then use the Child EventLoopGroup to execute specific commands. So ServerBootstrap has an additional childGroup and corresponding childHandler:

    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;
Copy the code

Because ServerBootstrap has two groups, ServerBootstrap contains a group method with two EventLoopGroups:

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) 
Copy the code

Remember the init method that bind has to implement? Let’s look at the logic of init in ServerBootstrap:

void init(Channel channel) { setChannelOptions(channel, newOptionsArray(), logger); setAttributes(channel, newAttributesArray()); ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<? >, Object>[] currentChildOptions = newOptionsArray(childOptions); final Entry<AttributeKey<? >, Object>[] currentChildAttrs = newAttributesArray(childAttrs); p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(final Channel ch) { 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)); }}); }}); }Copy the code

The first step is to set some properties of the channel, then get the pipeline for the channel through the channel.pipeline method, and then add a channelHandler to the pipeline.

These are all normal operations, but one thing to note is that the eventLoop that was registered with the channel at the end adds ServerBootstrapAcceptor to the pipeline.

It is obvious that the ServerBootstrapAcceptor itself should be a ChannelHandler whose main purpose is to accept connections:

    private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter
Copy the code

Let’s look at its channelRead method:

public void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); setAttributes(child, childAttrs); 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); }}Copy the code

Because the server accepts the connect operation of the client channel, the object in the corresponding channelRead is actually a channel. The received channel is called child. By giving the child channel add childHandler childOptions and childAttrs, the logic of a child channel can handle request is formed.

Finally, the Child Channel is registered with the Child Group, and the entire ServerBootstrapAcceptor accepts the Channel.

The best part here is to send the client channel to the server side through the channel on the server side, and then assign a handler to the Child Channel on the server side for specific business processing, very clever.

conclusion

By analyzing the structure and implementation logic of AbstractBootstrap, Bootstrap and ServerBootstrap, we believe that you have a general understanding of the startup process of Netty service. We’ll talk more about Channels and the all-important eventLoop in Netty later.

This article is available at www.flydean.com/03-1-netty-…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!