Análisis de código fuente de Netty (3) -procesamiento de nuevas conexiones del lado del servidor

Prefacio

En el análisis del código fuente de Netty (2): el análisis del proceso de inicio del servidorServerSocketChannel al final del proceso de enlace del puerto de escucha, ya hemos mencionado, después de que se complete la operación de enlace, Netty enviará pipeline.fireChannelActive()notificaciones de llamadas de tareas asincrónicas. El canal se ha activado, el channelActive()método del procesador de devolución de llamada. , Y el evento monitoreado por el servidor Channel se actualizará a SelectionKey.OP_ACCEPT. Por tanto, este artículo divide el establecimiento de una nueva conexión al servidor en dos pasos, el proceso se muestra en la figura

  1. Configuración del evento de supervisión SelectionKey.OP_ACCEPT
  2. Registro de la nueva conexión establecida por MainReactor en SubReactor

Inserte la descripción de la imagen aquí

1. Configuración del evento de supervisión SelectionKey.OP_ACCEPT

  1. AbstractUnsafe#bind()Al finalizar, ServerSocketChannelvincular el puerto del servidor de escucha, esta vez el canal ya es un Activeestado, enviará una notificación de tareas asíncronas activadas por el canal al hilo del bucle de eventos

    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. Consulte el diagrama de flujo para conocer el proceso de ingresar a la cola y programar la ejecución de tareas asincrónicas, y no se realizará ningún análisis específico aquí. Sabemos que el tratamiento de datos en Netty depende deComponente empresarialPara completar esta tarea se activaron las llamadas asincrónicas al aviso del DefaultChannelPipeline#fireChannelActive()método final

    public final ChannelPipeline fireChannelActive() {
          
          
         AbstractChannelHandlerContext.invokeChannelActive(head);
         return this;
     }
    
  3. Finalmente, los pasos anteriores para llamar a HeadContext#channelActive()un método, ingresaron al enlace bidireccional del procesador del controlador. Puede ver que hay dos cosas principales que se hacen en este método:

    1. ctx.fireChannelActive () llama al método de notificación para notificar al siguiente procesador de los eventos de activación del canal, por lo que vuelve a llamar al método channelActive () del siguiente procesador
    2. readIfIsAutoRead () decide si comenzar a leer datos automáticamente de acuerdo con la configuración de lectura automática, el valor predeterminado es lectura automática, llamará al método de lectura del canal
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
          
          
        ctx.fireChannelActive();
    
        readIfIsAutoRead();
    }
    
    private void readIfIsAutoRead() {
          
          
         if (channel.config().isAutoRead()) {
          
          
             channel.read();
         }
    }
    
  4. ChannelLos datos leídos son realmente dependientes DefaultChannelPipeline#read()y, finalmente, se tomará el nodo de cola de la lista enlazada del procesador bidireccional en la canalización para iniciar la operación de lectura.

    public final ChannelPipeline read() {
          
          
         tail.read();
         return this;
     }
    
  5. TailContext#read()Lógica principal del findContextOutbound()método a través de un método para encontrar un proceso Contexto de evento de salida desde la cola de la lista vinculada, es decir HeadContext, las llamadas de paquete dentro del read()Método del controlador

    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()La lógica de métodos rara vez, de hecho, llama al Unsafe#beginRead()método, el método para lograr esto es AbstractUnsafe#beginRead()la última llamada al AbstractNioChannel#doBeginRead()método. Se puede observar que la AbstractNioChannel#doBeginRead()lógica interna es relativamente concisa, lo principal es modificar el bit de operación del evento de escucha a través de las AbstractNioChannelpropiedades internas guardadas readInterestOp, y el valor de esta propiedad se pone a cuando arranca el servidor SelectionKey.OP_ACCEPT, es decirA través del paso actual, el servidor comienza a monitorear el nuevo evento de conexión.

    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. Registro de nueva conexión en SubReactor

  1. Procesamiento central del evento Eevnt que NioEventLoop, NioEventLoop#run()abrió un espacio de circulación, que NioEventLoop#processSelectedKeys()maneja todos los eventos relacionados con el Canal, el evento final correspondiente es un método de procesamiento NioEventLoop#processSelectedKey()para el SelectionKey.OP_ACCEPTevento será llamadounsafe.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()Es una llamada de interfaz, y el servidor se implementa como NioMessageUnsafe#read(), las cosas más importantes que hace este método son las siguientes:

    1. doReadMessages () que implementa la llamada interfaz NioServerSocketChannel # doReadMessages () Aceptar JDK incorporado conexión de interfaz, y se envasa en condiciones de controlar el evento operación del objeto devuelto,SelectionKey.OP_READNioSocketChannelA partir de esta nueva conexión se establece
    2. pipeline.fireChannelRead (readBuf.get (i)) toma el NioSocketChannel recién establecido en parámetros, llama al componente de procesamiento empresarial para procesarlo y finalmente llama al procesador integrado del servidor ServerBootstrapAcceptor#channelRead() Registre la nueva conexión de MainReactor a 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. El establecimiento de una nueva conexión y los componentes de procesamiento comercial en el proceso de llamar a la nueva conexión se pueden entender de acuerdo con el diagrama de flujo anterior. No lo analizaremos aquí, solo nos enfocaremos en el núcleo ServerBootstrapAcceptor#channelRead(). Podemos ver este método utilizado en la ServerBootstrappreservación de las opciones de configuración de SubReactor y la NioEventLoopGroupinstancia del procesador , por childGroupreferencia al canal recién creado registrado en 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)Procesos de registro y análisis del código fuente de Netty (2): el proceso de registro del proceso de inicio del servidor mencionó algunos pasos, el proceso incluirá el bucle de eventos SubReactor de creación y lanzamiento de subprocesos, así como la creación y configuración del canal en Pipleline. Cuando se complete el registro, el canal llamará a pipeline.fireChannelActive()la notificación del evento de activación del canal,Luego, en la primera parte de este artículo, se activa la modificación del bit de operación del evento de monitoreo en el canal, pero el bit de operación del evento de monitoreo establecido en este momento es SelectionKey.OP_READ

Supongo que te gusta

Origin blog.csdn.net/weixin_45505313/article/details/107232680
Recomendado
Clasificación