Netty source code analysis (3)-server new connection processing

Preface

In Netty source code analysis (2) - the server startup process analysis ServerSocketChannelat 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

  1. Setting of monitoring event SelectionKey.OP_ACCEPT
  2. Registration of the new connection established by MainReactor on SubReactor

Insert picture description here

1. Setting of monitoring event SelectionKey.OP_ACCEPT

  1. AbstractUnsafe#bind()Upon completion ServerSocketChannelBind listening server port, this time Channel is already a Activestate, will submit a notification Channel activated asynchronous tasks to the event loop thread

    public 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);
         }
    
  2. 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()method

    public final ChannelPipeline fireChannelActive() {
          
          
         AbstractChannelHandlerContext.invokeChannelActive(head);
         return this;
     }
    
  3. 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:

    1. 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
    2. 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();
         }
    }
    
  4. ChannelThe read data in is actually dependent DefaultChannelPipeline#read(), and finally the tail node of the two-way processor linked list in the Pipeline will be taken to start the read operation

    public final ChannelPipeline read() {
          
          
         tail.read();
         return this;
     }
    
  5. TailContext#read()Method main logic through findContextOutbound()a method to find a process Context outbound event from the tail of the linked list, i.e. HeadContext, the package calls within the Handler read()Method

    public 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;
     }
    
  6. HeadContext#read()Methods logic rarely, in fact, calls the Unsafe#beginRead()method, the method of achieving this is AbstractUnsafe#beginRead()the last call to the AbstractNioChannel#doBeginRead()method. It can be seen that the AbstractNioChannel#doBeginRead()internal logic is also relatively concise. The main thing to do is to modify the operation bit of the listening event through the AbstractNioChannelsaved internal attributes readInterestOp, and the value of this attribute is set to when the server starts SelectionKey.OP_ACCEPT, that is to sayThrough the current step, the server starts to monitor the new connection event

    protected 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

  1. Core processing Eevnt event that NioEventLoop, NioEventLoop#run()opened up a space for circulation, which NioEventLoop#processSelectedKeys()handles all events related to the Channel, the corresponding final event is a processing method NioEventLoop#processSelectedKey()for the SelectionKey.OP_ACCEPTevent 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());
         }
     }
    
  2. unsafe.read()It is an interface call, and the server is implemented as NioMessageUnsafe#read(), the more important things this method does are as follows:

    1. 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_READof NioSocketChannelthe returned object,From this new connection is established
    2. 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();
                 }
             }
         }
    
  3. 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 the ServerBootstrappreservation of SubReactor configuration options, and processor NioEventLoopGroupinstance, by childGroupreference to the newly created Channel registered on SubReactor

    public 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);
             }
         }
    
  4. 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 call pipeline.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 is SelectionKey.OP_READ

Guess you like

Origin blog.csdn.net/weixin_45505313/article/details/107232680