Article Directory
Preface
In Netty source code analysis (2) - the server startup process analysis ServerSocketChannel
at the end of the listening port binding process, we have already mentioned, after the binding operation is complete, Netty will submit asynchronous tasks call pipeline.fireChannelActive()
notification Channel has been activated, the callback processor channelActive()
method , And the event monitored by the server Channel will be updated to SelectionKey.OP_ACCEPT
. Therefore, this article divides the establishment of a new server connection into two steps, the process is shown in the figure
- Setting of monitoring event SelectionKey.OP_ACCEPT
- Registration of the new connection established by MainReactor on SubReactor
1. Setting of monitoring event SelectionKey.OP_ACCEPT
-
AbstractUnsafe#bind()
Upon completionServerSocketChannel
Bind listening server port, this time Channel is already aActive
state, will submit a notification Channel activated asynchronous tasks to the event loop threadpublic final void bind(final SocketAddress localAddress, final ChannelPromise promise) { assertEventLoop(); ...... boolean wasActive = isActive(); try { doBind(localAddress); } catch (Throwable t) { safeSetFailure(promise, t); closeIfClosed(); return; } if (!wasActive && isActive()) { invokeLater(new Runnable() { @Override public void run() { pipeline.fireChannelActive(); } }); } safeSetSuccess(promise); }
-
Refer to the flowchart for the process of entering the queue and scheduling execution of asynchronous tasks, and no specific analysis will be made here. We know that the processing of data in Netty depends onBusiness componentTo complete this task activated asynchronous calls to the notice of the final
DefaultChannelPipeline#fireChannelActive()
methodpublic final ChannelPipeline fireChannelActive() { AbstractChannelHandlerContext.invokeChannelActive(head); return this; }
-
Finally, the above steps to call
HeadContext#channelActive()
a method, entered Handler processor two-way link. You can see that there are two main things done in this method:- ctx.fireChannelActive() calls the notification method to notify the next processor of Channel activation events, thereby calling back the channelActive() method of the next processor
- readIfIsAutoRead() determines whether to start reading data automatically according to the automatic reading configuration. The default is automatic reading, and the read method of the Channel will be called
public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelActive(); readIfIsAutoRead(); } private void readIfIsAutoRead() { if (channel.config().isAutoRead()) { channel.read(); } }
-
Channel
The read data in is actually dependentDefaultChannelPipeline#read()
, and finally the tail node of the two-way processor linked list in the Pipeline will be taken to start the read operationpublic final ChannelPipeline read() { tail.read(); return this; }
-
TailContext#read()
Method main logic throughfindContextOutbound()
a method to find a process Context outbound event from the tail of the linked list, i.e.HeadContext
, the package calls within the Handlerread()
Methodpublic ChannelHandlerContext read() { final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeRead(); } else { Runnable task = next.invokeReadTask; if (task == null) { next.invokeReadTask = task = new Runnable() { @Override public void run() { next.invokeRead(); } }; } executor.execute(task); } return this; }
-
HeadContext#read()
Methods logic rarely, in fact, calls theUnsafe#beginRead()
method, the method of achieving this isAbstractUnsafe#beginRead()
the last call to theAbstractNioChannel#doBeginRead()
method. It can be seen that theAbstractNioChannel#doBeginRead()
internal logic is also relatively concise. The main thing to do is to modify the operation bit of the listening event through theAbstractNioChannel
saved internal attributesreadInterestOp
, and the value of this attribute is set to when the server startsSelectionKey.OP_ACCEPT
, that is to sayThrough the current step, the server starts to monitor the new connection eventprotected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { return; } readPending = true; final int interestOps = selectionKey.interestOps(); if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp); } }
2. Registration of new connection on SubReactor
-
Core processing Eevnt event that
NioEventLoop
,NioEventLoop#run()
opened up a space for circulation, whichNioEventLoop#processSelectedKeys()
handles all events related to the Channel, the corresponding final event is a processing methodNioEventLoop#processSelectedKey()
for theSelectionKey.OP_ACCEPT
event will be calledunsafe.read()
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); ...... 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) { unsafe.read(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }
-
unsafe.read()
It is an interface call, and the server is implemented asNioMessageUnsafe#read()
, the more important things this method does are as follows:- doReadMessages () that implements the interface NioServerSocketChannel # doReadMessages () call Accept JDK built-in interface connection, and packed into a position to monitor the operation event
SelectionKey.OP_READ
ofNioSocketChannel
the returned object,From this new connection is established - pipeline.fireChannelRead(readBuf.get(i)) takes the newly established NioSocketChannel into parameters, calls the business processing component to process it, and finally calls the built-in processor of the server
ServerBootstrapAcceptor#channelRead()
Register the new connection from MainReactor to SubReactor
public void read() { assert eventLoop().inEventLoop(); ...... try { try { do { 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()); } } } 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(); } } }
- doReadMessages () that implements the interface NioServerSocketChannel # doReadMessages () call Accept JDK built-in interface connection, and packed into a position to monitor the operation event
-
New connection establishment and business processing components in the process of calling the new connection can be understood according to the above flowchart. We will not analyze it here, we only focus on the core
ServerBootstrapAcceptor#channelRead()
. We can see this method used in theServerBootstrap
preservation of SubReactor configuration options, and processorNioEventLoopGroup
instance, bychildGroup
reference to the newly created Channel registered on SubReactorpublic void channelRead(ChannelHandlerContext ctx, Object msg) { final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); 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); } }
-
childGroup.register(child)
Registration processes and Netty source code analysis (2) - the server startup process registration process mentioned a few goes, the process will include SubReactor event loop of thread creation and launch, as well as Channel in Pipleline creation and configuration. When the registration is completed Channel will callpipeline.fireChannelActive()
the Channel activation event notification out,In the first part of this article, the modification of the monitoring event operation bit on the Channel is triggered, but the monitoring event operation bit set at this time isSelectionKey.OP_READ