netty source code-client initialization

Netty client code example:

public Test connect(int port, String host, final String nickName) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                }
            });
            //发起同步连接操作
            ChannelFuture channelFuture = bootstrap
                    .connect(host, port)
                    .sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //关闭,释放线程资源
            group.shutdownGracefully();
        }
        return this;
    }
    public static void main(String[] args) {
        new Test().connect(8080, "localhost", "Tom");
    }

Set NioSocketChannel.class in the channel, the following method of AbstractBootstrap will be called

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

The ReflectiveChannelFactory implements the ChannelFactory interface, which provides the only method, namely the newChannel() method. ChannelFactory, as the name implies, is the factory class for creating Channel. Entering the newChannel() method of ReflectiveChannelFactory, we see that its implementation code is as follows:

public ReflectiveChannelFactory(Class<? extends T> clazz) {
    ObjectUtil.checkNotNull(clazz, "clazz");
    try {
        this.constructor = clazz.getConstructor();
    } catch (NoSuchMethodException e) {
        throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                                           " does not have a public non-arg constructor", e);
    }
}
@Override
public T newChannel() {
    try {
        return constructor.newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
    }
}

According to the above code:

1. The ChannelFactory implementation class in Bootstrap is ReflectiveChannelFactory.

2. The specific type of Channel created by the channel() method is NioSocketChannel.

The process of Channel instantiation is actually to call ChannelFactory's newChannel() method, and the specific type of Channel instantiated is related to the parameters of the channel() method passed in when Bootstrap is initialized. So for the client Bootstrap, the Channel instance created is NioSocketChannel

Initialization of the client channel

We have already known how to set the type of a Channel, and learned that Channel is instantiated by ChannelFactory's newChannel() method, so where is the ChannelFactory's newChannel() method called? Continue tracking, we found that its call chain is as follows :

In the initAndRegister() of AbstractBootstrap, the newChannel() of ChannelFactory() is called to create an instance of NioSocketChannel. The source code is as follows:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //调用的是channelFactory接口的实现类ReflectiveChannelFactory中的newChannel
            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);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

In the newChannel() method, use the reflection mechanism to call the newInstance() method of the class object to create a new Channel instance, which is equivalent to calling the default constructor of NioSocketChannel. The default constructor code of NioSocketChannel is as follows:

public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
 }
public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
 }
 private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            /**
             *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
             *  {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise.
             *
             *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
             */
            return provider.openSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
 }

The code here is more critical. We see that in this constructor, newSocket() is called to open a new Java NIO SocketChannel:

Then the parent class, the constructor of AbstractNioByteChannel will be called:

public NioSocketChannel(SocketChannel socket) {
        this(null, socket);
 }
public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }

And the incoming parameter parent is null, ch is the Java NIO SocketChannel object created by calling newSocket() just now, so the newly created

The parent in the NioSocketChannel object is temporarily null.

 protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
  }

Then it will continue to call the constructor of the parent class AbstractNioChannel and pass in the actual parameter readInterestOp =SelectionKey.OP_READ:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                logger.warn(
                            "Failed to close a partially initialized socket.", e2);
            }
            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
  }

Then continue to call the constructor of the parent class AbstractChannel:

protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

At this point, the initialization of NioSocketChannel is complete, and we can briefly summarize the work done by NioSocketChannel initialization:

1. Call NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) to open a new Java NIOSocketChannel.

2. Attributes that need to be initialized in AbstractChannel (Channel parent):

id: Each Channel has a unique id.

parent: The attribute is set to null.

unsafe: instantiate an unsafe object through newUnsafe(), its type is AbstractNioByteChannel.NioByteUnsafe inner class.

pipeline: is a newly created instance of new DefaultChannelPipeline(this).

3. Properties in AbstractNioChannel:

ch: Assigned to Java SocketChannel, which is the Java NIO SocketChannel returned by the newSocket() method of NioSocketChannel.

readInterestOp: Assigned to SelectionKey.OP_READ

ch: is configured as non-blocking, that is, call ch.configureBlocking(false)

4. Properties in NioSocketChannel:

config = new NioSocketChannelConfig(this, socket.socket())

Initialization of the Unsafe field

We briefly mentioned that in the process of instantiating NioSocketChannel, it will be called in the constructor of the parent class AbstractChannel

newUnsafe() to obtain an unsafe instance. So how is unsafe initialized? What is its function?

In fact, unsafe is particularly critical. It encapsulates the operation of the Java bottom Socket, so it is actually an important bridge between the upper layer of Netty and the bottom layer of Java. So let's take a look at the methods provided by the Unsafe interface:

interface Unsafe {
    RecvByteBufAllocator.Handle recvBufAllocHandle();

    SocketAddress localAddress();

    SocketAddress remoteAddress();

    void register(EventLoop eventLoop, ChannelPromise promise);

    void bind(SocketAddress localAddress, ChannelPromise promise);

    void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);

    void disconnect(ChannelPromise promise);

    void close(ChannelPromise promise);

    void closeForcibly();

    void deregister(ChannelPromise promise);

    void beginRead();

    void write(Object msg, ChannelPromise promise);

    void flush();

    ChannelPromise voidPromise();

    ChannelOutboundBuffer outboundBuffer();
} 

It can be seen from the source code that these methods are actually corresponding to the operation of the underlying Java Socket. Continue back to the construction method of AbstractChannel, where newUnsafe() is called to obtain a new unsafe object, and the newUnsafe() method is rewritten in NioSocketChannel. Look at the code

@Override
protected AbstractNioUnsafe newUnsafe() {
	return new NioSocketChannelUnsafe();
}

The newUnsafe() method of NioSocketChannel will return an instance of NioSocketChannelUnsafe. From here, we can confirm that the unsafe field in the instantiated NioSocketChannel is actually an instance of NioSocketChannelUnsafe.

Initialization of Pipeline

Above we analyzed the general initialization process of NioSocketChannel, but we missed a key part, namely the initialization of ChannelPipeline. In the notes of the Pipeline, it says "Each channel has its own pipeline and it is created automatically when a new channel is created.", we know that when a Channel is instantiated, a ChannelPipeline must be instantiated. And we did see in the constructor of AbstractChannel that the pipeline field was initialized to an instance of DefaultChannelPipeline. So let’s take a look at what the DefaultChannelPipeline constructor does

 protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
 }

The constructor of DefaultChannelPipeline needs to pass in a channel, and this channel is actually the NioSocketChannel we instantiated, and DefaultChannelPipeline will store this NioSocketChannel object in the channel field. There are also two special fields in DefaultChannelPipeline, namely head and tail. These two fields are the head and tail of the doubly linked list. In fact, in DefaultChannelPipeline, a doubly linked list with AbstractChannelHandlerContext as the node element is maintained. This linked list is the key to Netty's implementation of the Pipeline mechanism.

In the linked list, the head is a ChannelOutboundHandler, and the tail is a ChannelInboundHandler. Then look at the HeadContext constructor:

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
}

 

Initialization of EventLoop

Going back to the initial ChatClient user code, we instantiated a NioEventLoopGroup object at the very beginning, so we will trace the initialization process of EventLoop from its constructor.

NioEventLoop has several overloaded constructors, but the content is not much different. In the end, they are all called the constructor of the parent class MultithreadEventLoopGroup:

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

One interesting thing is that if the number of threads we pass in nThreads is 0, then Netty will set the default number of threads for us

DEFAULT_EVENT_LOOP_THREADS, and how is the default number of threads determined?

In fact, it is very simple. In the static code block, the value of DEFAULT_EVENT_LOOP_THREADS will be determined first:

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

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

Netty will first get the value of "io.netty.eventLoopThreads" from the system properties. If we don't set it, then it will return to the default value: the number of processor cores * 2. Back to the MultithreadEventLoopGroup constructor will continue to call the constructor of the parent class MultithreadEventExecutorGroup:

 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        //*
        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) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
        //*
        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);
    }
@Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
        // Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
        // The 64-bit long solves this by placing the overflow so far into the future, that no system
        // will encounter this in practice.
        private final AtomicLong idx = new AtomicLong();
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }

The main meaning of the above code logic is: that is, if nThreads is a power of 2, use PowerOfTwoEventExecutorChooser, otherwise use GenericEventExecutorChooser. Both of these Choosers override the next() method. The main function of the next() method is to cyclically shift the array index, as shown in the following figure:

The logic of this operation is actually very simple, that is, each time the index is incremented and the array length is modulo: idx.getAndIncrement()% executors.length. But even a very simple array index operation, Netty has helped us optimize it. Because at the bottom of the computer, & and% are more efficient.

Well, we should have been very clear about the processing logic in MultithreadEventExecutorGroup by analyzing it, so we can briefly summarize:

1. Create a SingleThreadEventExecutor array of size nThreads.

2. Create different Choosers according to the size of nThreads, that is, if nThreads is a power of 2, use

PowerOfTwoEventExecutorChooser, otherwise use GenericEventExecutorChooser. No matter which Chooser is used, their functions are the same, that is, a suitable EventExecutor instance is selected from the children array.

3. Call the newChhild() method to initialize the children array.

According to the above code, we can also know: MultithreadEventExecutorGroup maintains an EventExecutor array internally, and Netty's EventLoopGroup implementation mechanism is actually built on MultithreadEventExecutorGroup. Whenever Netty needs an EventLoop, it will call the next() method to get an available EventLoop. The last part of the above code is the newChild() method, which is an abstract method whose task is to instantiate the EventLoop object. Let's track its code. it can be discovered. This method is implemented in the NioEventLoopGroup class, and its content is very simple:

@Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
    }

In fact, the logic is very simple: instantiate a NioEventLoop object, and then return the NioEventLoop object. Finally, summarize the initialization process of the entire EventLoopGroup:

1. EventLoopGroup (actually MultithreadEventExecutorGroup) internally maintains an array of type EventExecutor children,

Its size is nThreads, which constitutes a thread pool.

2. If we specify the thread pool size when instantiating NioEventLoopGroup, then nThreads is the specified value, and vice versa

Number of processor cores * 2.

3. The newChild() abstract method is called in MultithreadEventExecutorGroup to initialize the children array.

4. The abstract method newChild() is implemented in NioEventLoopGroup, and it returns a NioEventLoop instance.

5. NioEventLoop property assignment:

provider: Obtain a SelectorProvider through SelectorProvider.provider() in the NioEventLoopGroup constructor.

selector: In the NioEventLoop constructor, obtain a selector object by calling the provider.openSelector() method.

Channel registered to Selector

In the previous analysis, we mentioned that Channel will be initialized in Bootstrap's initAndRegister(), but this method will also register the initialized Channe in the selector of NioEventLoop. Next, let's analyze the process of Channel registration.

Recall the initAndRegister() method of AbstractBootstrap:

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);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

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

When the Channel is initialized, the group().register() method will be called to register the Channel with the selector. If we continue to follow up, we will

It is found that the call chain is as follows:

 

What exactly is done in the AbstractChannel$AbstractUnsafe.register() method?

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

First, assign eventLoop to the eventLoop property of Channel, and we know that this eventLoop object is actually

Obtained by the next() method of MultithreadEventLoopGroup. According to our previous analysis, we can determine that the eventLoop object returned by the next() method is an instance of NioEventLoop. The register() method then calls the register0() method:

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) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

The register0() method calls the doRegister() method of AbstractNioChannel

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

See that the javaChannel() method has been known before. It returns a Java NIO SocketChannel object. Here I

We registered this SocketChannel to the selector associated with eventLoop.

Let's summarize the registration process of Channel:

1. First, in the initAndRegister() method of AbstractBootstrap, call group().register(channel)

The register() method of MultithreadEventLoopGroup.

2. In the register() of MultithreadEventLoopGroup, call the next() method to obtain an available SingleThreadEventLoop, and then call its register() method.

3. In the register() method of SingleThreadEventLoop, call channel.unsafe().register(this, promise) method to get

The channel's unsafe() underlying operation object, and then unsafe's register() is called.

4. In the register() method of AbstractUnsafe, call the register0() method to register the Channel object.

5. In the register0() method of AbstractUnsafe, call the doRegister() method of AbstractNioChannel.

6. The doRegister() method of AbstractNioChannel registers the Java NIO SocketChannel corresponding to Channel to an eventLoop selector through javaChannel().register(eventLoop().selector, 0, this), and associates the current Channel with the SocketChannel as an attachment .

In general, the work done by the Channel registration process is to associate the Channel with the corresponding EventLoop, so this is also reflected in the Netty

Each Channel is associated with a specific EventLoop, and all IO operations in this Channel are executed in this EventLoop; when the Channel and EventLoop are associated, the register() of the SocketChannel object of the underlying Java NIO will continue to be called Method, register the SocketChannel of the underlying Java NIO to the specified selector. Through these two steps, the registration process of Netty to Channel is completed.

The process of adding Handler

One of Netty's powerful and flexible features is the custom handler mechanism based on Pipeline. Based on this, we can freely combine various handlers to complete business logic like adding plug-ins. For example, if we need to process HTTP data, we can add a handler for HTTP encoding and decoding before the pipeline, and then add our own business logic handler, so that the data flow on the network is the same as passing through a pipeline, from different It flows through the handler, performs encoding and decoding, and finally arrives in our custom handler.

Speaking of this, some friends will definitely be curious. Since this pipeline mechanism is so powerful, how is it implemented? I am not going to explain it in detail here. In this section, we will start with simple and experience first How and when the custom handler is added to ChannelPipeline. First we look at the following user code snippet:

ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //先添加websocket相关的编解码器和协议处理器
                pipeline.addLast("msgDecoder", new ProtocolDecoder());// 解码器
                pipeline.addLast("msgEncoder", new ProtocolEncoder());// 编码器
                pipeline.addLast("idleStateHandler", new IdleStateHandler(serverConfig.getAllIdleSecond(), 0, 0));// 定时器,秒
                pipeline.addLast("handler", new WebsocketRouterHandler());// 消息总处理器WebsocketRouterHandler

            }

        };

This code snippet implements the addition function of the handler. We see that the handler() method of Bootstrap receives a ChannelHandler, and the parameter we pass is an anonymous class derived from the abstract class ChannelInitializer, which of course also implements the ChannelHandler interface. Let's take a look at the mystery in the ChannelInitializer class:

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
    // We use a Set as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
    // ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
    private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
            new ConcurrentHashMap<ChannelHandlerContext, Boolean>());

    /**
     * This method will be called once the {@link Channel} was registered. After the method returns this instance
     * will be removed from the {@link ChannelPipeline} of the {@link Channel}.
     *
     * @param ch            the {@link Channel} which was registered.
     * @throws Exception    is thrown if an error occurs. In that case it will be handled by
     *                      {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
     *                      the {@link Channel}.
     */
    protected abstract void initChannel(C ch) throws Exception;

    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        // Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
        // the handler.
        if (initChannel(ctx)) {
            // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
            // miss an event.
            ctx.pipeline().fireChannelRegistered();

            // We are done with init the Channel, removing all the state for the Channel now.
            removeState(ctx);
        } else {
            // Called initChannel(...) before which is the expected behavior, so just forward the event.
            ctx.fireChannelRegistered();
        }
    }

    /**
     * Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
        }
        ctx.close();
    }

    /**
     * {@inheritDoc} If override this method ensure you call super!
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // This should always be true with our current DefaultChannelPipeline implementation.
            // The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
            // surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
            // will be added in the expected order.
            if (initChannel(ctx)) {

                // We are done with init the Channel, removing the initializer now.
                removeState(ctx);
            }
        }
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        initMap.remove(ctx);
    }

    @SuppressWarnings("unchecked")
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
                // We do so to prevent multiple calls to initChannel(...).
                exceptionCaught(ctx, cause);
            } finally {
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }

    private void removeState(final ChannelHandlerContext ctx) {
        // The removal may happen in an async fashion if the EventExecutor we use does something funky.
        if (ctx.isRemoved()) {
            initMap.remove(ctx);
        } else {
            ctx.executor().execute(new Runnable() {
                @Override
                public void run() {
                    initMap.remove(ctx);
                }
            });
        }
    }
}

ChannelInitializer is an abstract class. It has an abstract method initChannel(). The anonymous class we saw implements this method.

And add the custom handler in this method. So where is initChannel() called? In fact, it is in the channelRegistered() method of ChannelInitializer.

Next, focus on the channelRegistered() method. From the above source code, we can see that in the channelRegistered() method, the initChannel() method will be called, the custom handler will be added to the ChannelPipeline, and then the ctx.pipeline().remove(this) method will be called Removed from ChannelPipeline. The above analysis process is shown in the following picture:

At the beginning, there were only three handlers in ChannelPipeline: head, tail and the ChannelInitializer we added.

Then after the initChannel() method is called, a custom handler is added:

Guess you like

Origin blog.csdn.net/madongyu1259892936/article/details/110442812