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