Netty source code analysis NioSocketChannel source code day reading

Posted by monkey72 on Fri, 18 Feb 2022 22:12:27 +0100

Opening words

I hope you can learn this article with questions. Here I put forward two questions for you. The first Netty is where a new connection is detected. How does the second new connection register with the nioeventloop thread.

netty new connection access processing logic:

To detect new connection access, poll the accept event through the selector bound to the serverchannel, and create a nety NioSocketChannel based on the jdk channel. Then netty assigns a Nioeventloop to the client channel and registers the channel with the selector corresponding to the Nioeventloop. Then we can register the read event with the selector corresponding to the channel.

Source code analysis

NioEventloop.java
// Logic for handling new connection access:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                // If the channel implementation throws an exception because there is no event loop, we ignore this
                // because we are only trying to determine if ch is registered to this event loop and thus has authority
                // to close ch.
                return;
            }
            // Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
            // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
            // still healthy and should not be closed.
            // See https://github.com/netty/netty/issues/5125
            if (eventLoop != this || eventLoop == null) {
                return;
            }
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            Here is the key point. Entering this method will execute this logic to judge whether there is a new connection. If there is a new connection op_accept
            Event, the following will be executed unsafe.read();
                unsafe.read();
                if (!ch.isOpen()) {
                    // Connection already closed - no need to handle write.
                    return;
                }
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

Next, go to unsafe Read() method:

AbstractNioMessageChannel.java
 Inner class NioMessageUnsafe
     @Override
        public void read() {
        // Judge whether it is currently in the Nioeventloop of the server, otherwise an error will be reported
            assert eventLoop().inEventLoop();
            // server side config
            final ChannelConfig config = config();
            // pipeline on server side
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                       there doReadMessages It's the key point. It will be created here jdk Bottom channel,
                       Then packaged into netty of niochannel,Put the read connection in readBuf In this temporary container.
                       Let's follow up on this doReadMessages(readBuf)
                   
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } 
NioServerSocketChannel.java
 @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
       // Create the channel at the bottom of jdk
        SocketChannel ch = javaChannel().accept();

        try {
            if (ch != null) {
            // It is encapsulated into NioSocketChannel and then added to the temporary container. At the same time, the server channel is also passed in the construction method
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            logger.warn("Failed to create a new channel from an accepted socket.", t);

            try {
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

So here we answer the first question, where is the new connection detected? Process the io event in processSelectedKey() of Nioeventloop. When it is found that it is an accept event, it will call unsafe Read () method to create niosocketchannel, which encapsulates the underlying channel of jdk.

Creation of NioSocketChannel

NioSocketChannel construction method

    public NioSocketChannel(Channel parent, SocketChannel socket) {
       // Construction method of substitute parent class AbstractNioByteChannel
        super(parent, socket);
        // Create a config configuration class, where tcpnodelay will be set to true, that is, small packets will also be sent to reduce the delay.
        config = new NioSocketChannelConfig(this, socket.socket());
    }
    AbstractNioByteChannel.java
        protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        // SelectionKey.OP_READ means that when this channel is subsequently bound to the selector, it means that I am interested in reading events
       // Interested, please give it to me if there is a reading event
        super(parent, ch, SelectionKey.OP_READ);
    }
    Keep following
    AbstractNioChannel.java
        protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        // Save event ID of interest
        this.readInterestOp = readInterestOp;
        try {
        // Set io mode to non blocking mode
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
    Follow up supr(parent)method
    AbstractChannel.java
        protected AbstractChannel(Channel parent) {
        // Create these three basic components
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

Classification of key channel s

1: NioServerSocketChannel
2: NioSocketChannel
3: Unsafe: implement the underlying protocol of each channel
The following is the hierarchical diagram of channel
channel interface: defines the top-level framework for network reading and writing
AbstractChannel: the implementation of channel. All channel function abstractions can be implemented by this abstract class

As you can see, usafe id,pipeline,eventloop Are stored abstractly in this class
    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    private final DefaultChannelPipeline pipeline;
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);

    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    private volatile EventLoop eventLoop;
    private volatile boolean registered;

    /** Cache for the string representation of this channel */
    private boolean strValActive;

AbstractNiochannel: responsible for nio related parts. It mainly uses selector to listen to read-write events.

Whether it's the server channel Or client channel,Register to selector There will be one when I go to school selectionkey,It is stored in the member variable of this class
SelectableChannel ch Saved the server channel Or client channel Bottom jdk channel
    private final SelectableChannel ch;
    This is a reading or accept event
    protected final int readInterestOp;
    volatile SelectionKey selectionKey;
    boolean readPending;

Client channel: AbstractNioByteChannel, NioSocketchannel, NioByteUnsafe (which is a member variable of AbstractNioByteChannel)
Server channel: AbstractNioMessageChannel, NioServerSocketchannel, NioMessageUnsafe (a member variable of AbstractNioMessageChannel)
Both AbstractNioByteChannel and AbstractNioMessageChannel inherit AbstractNiochannel, which indicates that they poll io events through selector, but care about different events. As shown below

    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

unsafe: realize the read-write abstraction of each channel. For the read client, the read of the channel is to read data, while the read of the server channel is to read a connection.
Server read:
Client read: read io data

How to allocate Eventloop and register selector for new connection establishment

Here is a very important point: when creating a channel on the server side, if there is a new connection, it will call unsafe Read() event. Next, this code has two very important logic, which will be analyzed next

 private final class NioMessageUnsafe extends AbstractNioUnsafe {

        private final List<Object> readBuf = new ArrayList<Object>();

        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                    //Put the read connection into the temporary container readbuf
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // Call pipeline to propagate the connection downward from the head node. Here, the fireChannelread method is passed because
                    // Connection, so go to the last call channelread () method interface, will eventually call to
                    //  The bootschandler method of the serveraccessor class
                    // Note that the serverbootstrap acceptor is in serverbootstrap Created in init() method
                    //The implementation of channelread () method is analyzed in detail below
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }
    }
  @Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            for (Entry<ChannelOption<?>, Object> e: childOptions) {
                try {
                    if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
                        logger.warn("Unknown channel option: " + e);
                    }
                } catch (Throwable t) {
                    logger.warn("Failed to set a channel option: " + child, t);
                }
            }

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

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

Three main things have been done here (childhandler, options and attrs are user-defined codes)
Here, let's sort out the logic:
First start Nioeventloop, and then execute the processSelectedKey method to judge whether there is a new connection access event. If so, call unsave Read () creates a new connection in it, and then calls pipeline.. Firechannel read (readbuf. Get (I)), because it is a connection, it will eventually call serverbootstrap channelread method of serverbootstrap acceptor. What you do is what you want to do as shown in the figure above.
Next, let's talk about Nioeventloop.
Registration of NioSocketChannel read events

Topics: Java Netty Back-end source code