inicialización del servidor de código fuente netty

Creación de NioServerSocketChannel

Mirando el código del servidor de nuevo, llamamos al método channel (NioServerSocketChannel.class) de ServerBootstarap, y el parámetro pasado es el objeto NioServerSocketChannel.class. De esta manera, siguiendo el mismo proceso del código del cliente, podemos determinar que la instanciación de NioServerSocketChannel también se realiza a través de la clase de fábrica ReflectiveChannelFactory, y al campo clazz en ReflectiveChannelFactory se le asigna el valor de NioServerSocketChannel.class, por lo que cuando el método newChannel () de ReflectiveChannel se llama Se puede obtener una instancia de NioServerSocketChannel. El código fuente del método newChannel () es el siguiente:

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
          
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }
​
    @Override
    public T newChannel() {
        try {
            //通过反射调用下面代码
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }

Finalmente, resumamos:

1. La clase de implementación de ChannelFactory en ServerBootstrap es ReflectiveChannelFactory.

2. El tipo específico de canal creado es NioServerSocketChannel.

El proceso de instanciación de Channel es en realidad llamar al método newChannel () de ChannelFactory, y el tipo concreto de Channel instanciado es el parámetro real que se pasa al método channel () cuando se inicializa ServerBootstrap. Por lo tanto, el ServerBootstrap del lado del servidor en el caso de código anterior crea el canal. Una instancia es una instancia de NioServerSocketChannel.

Inicialización del canal del servidor

Analicemos el proceso de creación de instancias de NioServerSocketChannel, primero observemos el diagrama de jerarquía de clases de NioServerSocketChannel:

Primero, rastreemos la estructura predeterminada de NioServerSocketChannel. Similar a NioSocketChannel, el constructor llama a newSocket () para abrir un Java NIO Socket. Sin embargo, debe tenerse en cuenta que el método newSocket () del cliente llama a openSocketChannel () y el newSocket () del servidor llama a openServerSocketChannel (). Como su nombre lo indica, uno es el Java SocketChannel del lado del cliente y el otro es el Java ServerSocketChannel del lado del servidor. Mira el código:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            /**
             *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
             *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
             *
             *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
             */
            
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException(
                    "Failed to open a server socket.", e);
        }
    }
​
    private final ServerSocketChannelConfig config;
​
    /**
     * Create a new instance,通过反射创建,调用构造
     */
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

A continuación, se llamará al método de construcción sobrecargado:

public NioServerSocketChannel (canal ServerSocketChannel) { 
        super (nulo, canal, SelectionKey.OP_ACCEPT); 
        config = new NioServerSocketChannelConfig (esto, javaChannel (). socket ()); 
 }

En este método de construcción, el parámetro que se pasa al llamar al método de construcción de la clase principal es SelectionKey.OP_ACCEPT. A modo de comparación, revisemos que cuando se inicializa el canal del cliente, el parámetro pasado es SelectionKey.OP_READ. Una vez que se inicia el servicio, debe escuchar la solicitud de conexión del cliente, por lo que aquí configuramos SelectionKey.OP_ACCEPT, que es para notificar al selector que estamos interesados ​​en la solicitud de conexión del cliente.

Luego compare y analice con el cliente, el constructor de la clase padre NioServerSocketChannel -> AbstractNioMessageChannel -> AbstractNioChannel -> AbstractChannel se llamará paso a paso. Del mismo modo, cree una instancia de una canalización insegura en AbstractChannel:

protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
 }

Sin embargo, debe tenerse en cuenta aquí que insegura en el lado del cliente es una instancia de AbstractNioByteChannel # NioByteUnsafe, e insegura en el lado del servidor es una instancia de AbstractNioMessageChannel.AbstractNioUnsafe. Debido a que AbstractNioMessageChannel reescribe el método newUnsafe (), su código fuente es el siguiente:

@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

Finalmente, para resumir, la lógica de ejecución durante la instanciación de NioServerSocketChannel:

1. Llame al método NioServerSocketChannel.newSocket (DEFAULT_SELECTOR_PROVIDER) para abrir un nuevo NIO de Java

ServerSocketChannel

2. AbstractChannel se inicializa y se le asigna un atributo:

padre: establecido en nulo

inseguro: crea una instancia de un objeto inseguro a través de newUnsafe (), el tipo es AbstractNioMessageChannel # AbstractNioUnsafe

pipeline: crea una instancia de DefaultChannelPipeline

3. Los atributos asignados en AbstractNioChannel:

ch: Asignado al ServerSocketChannel de Java NIO, llame al método newSocket () de NioServerSocketChannel para obtenerlo.

readInterestOp: la asignación predeterminada es SelectionKey.OP_ACCEPT.

ch está configurado como no bloqueante, llame al método ch.configureBlocking (false).

4. Los atributos asignados en NioServerSocketChannel:

config = new NioServerSocketChannelConfig (esto, javaChannel (). socket ())

Inicialización de ChannelPipeline

Arriba analizamos el proceso de inicialización general de NioServerSocketChannel, pero nos perdimos una parte clave, a saber, la inicialización de ChannelPipeline. En las notas de Pipeline, dice "Cada canal tiene su propio pipeline y se crea automáticamente cuando se crea un nuevo canal", sabemos que cuando se crea una instancia de un canal, se debe instanciar una ChannelPipeline. Y vimos en el constructor de AbstractChannel que el campo de canalización se inicializó en una instancia de DefaultChannelPipeline. Así que echemos un vistazo a lo que hace el constructor DefaultChannelPipeline

protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);
​
        tail = new TailContext(this);
        head = new HeadContext(this);
​
        head.next = tail;
        tail.prev = head;
    }

El constructor de DefaultChannelPipeline necesita pasar en un canal, y este canal es en realidad el NioServerSocketChannel que instanciamos, y DefaultChannelPipeline almacenará este objeto NioServerSocketChannel en el campo del canal. También hay dos campos especiales en DefaultChannelPipeline, a saber, cabeza y cola, que son la cabeza y la cola de la lista doblemente enlazada. De hecho, en DefaultChannelPipeline, se mantiene una lista doblemente enlazada con AbstractChannelHandlerContext como elemento de nodo Esta lista enlazada es la clave para la implementación de Netty del mecanismo Pipeline.

En la lista vinculada, el encabezado es ChannelOutboundHandler y el final es ChannelInboundHandler. Luego mire el constructor HeadContext:

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
 }

Canal de servidor registrado en Selector

Resumamos el proceso de registro de Channel:

1. Primero, en el método initAndRegister () de AbstractBootstrap, llame a group (). Register (channel)

El método register () de MultithreadEventLoopGroup.

2. En el registro () de MultithreadEventLoopGroup, llame al método next () para obtener un SingleThreadEventLoop disponible, y luego llame a su método register ().

3. En el método register () de SingleThreadEventLoop, llame al método channel.unsafe (). Register (this, promise) para obtener

Se llama al objeto de operación subyacente inseguro () del canal, y luego se llama al registro inseguro ().

4. En el método register () de AbstractUnsafe, llame al método register0 () para registrar el objeto Channel.

5. En el método register0 () de AbstractUnsafe, llame al método doRegister () de AbstractNioChannel.

6. El método doRegister () de AbstractNioChannel registra Java NIO SocketChannel correspondiente al canal en un selector eventLoop a través de javaChannel (). Register (eventLoop (). Selector, 0, this), y asocia el canal actual como un adjunto con ServerSocketChannel .

En general, el trabajo que realiza el proceso de registro del Canal es asociar el Canal con el EventLoop correspondiente, por lo que esto también se refleja en el Netty

Cada canal está asociado con un EventLoop específico, y todas las operaciones de IO en este canal se realizan en este EventLoop; cuando el canal y el EventLoop están asociados, el registro () del objeto ServerSocketChannel del Java NIO subyacente continuará llamándose Método, registre ServerSocketChannel del Java NIO subyacente en el selector especificado. A través de estos dos pasos, se completa el proceso de registro de Netty to Channel.

bossGroup 与 workerGroup

En el lado del cliente, inicializamos un objeto EventLoopGroup, y en el lado del servidor, configuramos dos

EventLoopGroup, uno es bossGroup y el otro es workerGroup. Entonces, ¿para qué se utilizan estos dos EventLoopGroup? Exploremos en detalle a continuación. De hecho, bossGroup solo se usa para aceptar en el lado del servidor, es decir, se usa para procesar la nueva solicitud de conexión del cliente. Podemos comparar a Netty con un restaurante. BossGroup es como un administrador del vestíbulo. Cuando los clientes vienen a comer al restaurante, el director del vestíbulo guiará a los clientes para que se sienten y sirvan té y agua. El grupo trabajador es el chef que realmente trabaja. Son responsables de la operación IO del canal de conexión del cliente: cuando el lobby experimenta la recepción de clientes, los clientes pueden tomar un descanso y los chefs de la cocina trasera (grupo trabajador) comienzan a ocuparse preparando Se acabó la comida. En cuanto a la relación entre bossGroup y workerGroup, podemos utilizar la siguiente figura para mostrar. También hemos analizado los capítulos anteriores. Aquí lo consolidaremos:

 

Primero, bossGroup del servidor monitorea constantemente si hay una conexión de cliente. Cuando se encuentra una nueva conexión de cliente, bossGroup inicializará varios recursos para esta conexión, y luego seleccionará un EventLoop del workerGroup para enlazar con este cliente. Termine la conexión. Luego, el siguiente proceso de interacción servidor-cliente se completa en el EventLoop asignado aquí. No hay pruebas para hablar, hablemos con el código fuente. Primero, cuando se inicializa ServerBootstrap, se llama a b.group (bossGroup, workerGroup) para configurar dos EventLoopGroups. Después de rastrearlo, veremos:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
        return this;
    }

El método bind () activará la siguiente cadena de llamadas:

AbstractBootstrap.bind () -> AbstractBootstrap.doBind () -> AbstractBootstrap.initAndRegister ()

Lejos del código fuente, encontramos que el método initAndRegister () de AbstractBootstrap ya es nuestro viejo amigo. Lo hemos tratado mucho al analizar el programa cliente. Ahora repasemos este método:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        //*
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

Aquí, el método group () devuelve el bossGroup que mencionamos anteriormente, y el canal aquí es en realidad una instancia de NioServerSocketChannel, por lo que podemos suponer que group (). Register (channel) debería asociar bossGroup y NioServerSocketChannel. Entonces, ¿dónde se relaciona exactamente workerGroup con NioServerSocketChannel? Continuamos mirando el método init (canal):

@Override
    void init(Channel channel) {
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
        }
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

De hecho, el método init () se ha reescrito en ServerBootstrap. En el fragmento de código anterior, podemos ver que agrega un

ChannelInitializer, y este ChannelInitializer agrega un controlador ServerBootstrapAcceptor muy crítico. Con respecto al proceso de agregar e inicializar el controlador, lo dejamos en los siguientes capítulos para un análisis detallado. Ahora, centrémonos en la clase ServerBootstrapAcceptor. El método channelRead () se reescribe en ServerBootstrapAcceptor, y su código principal es el siguiente:

@Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

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

El childGroup en ServerBootstrapAcceptor es el currentChildGroup pasado para construir este objeto, que es el objeto workerGroup. El canal aquí es una instancia de NioSocketChannel, por lo que el método register () de childGroup aquí es asociar un cierto EventLoop en el workerGroup con el NioSocketChannel. Siendo ese el caso, la pregunta ahora es ¿dónde se llama el método channelRead () de ServerBootstrapAcceptor? De hecho, cuando un cliente se conecta al servidor, el ServerSocketChannel del NIO subyacente de Java tendrá un evento SelectionKey.OP_ACCEPT listo, y luego lo hará Llame al método doReadMessages () de NioServerSocketChannel:

protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        //创建java的socket
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
 }

/**
     * @param
     * @return
     * @description: 创建的netty的服务端的socket
     * @author madongyu
     * @date 2020/10/29 15:36
     */
    @Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioMessageUnsafe();
    }

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 {
                        //*
                        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();
                }
            }
        }
    }


@Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                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;
    }

En el método doReadMessages (), el objeto SocketChannel recién conectado del cliente se obtiene llamando al método javaChannel (). Accept (), y luego se crea una instancia de NioSocketChannel y se pasa el objeto NioServerSocketChannel (es decir, esto). Se puede ver que el canal de clase padre del NioSocketChannel que creamos es una instancia de NioServerSocketChannel. A continuación, a través del mecanismo ChannelPipeline de Netty, el evento de lectura se envía a cada controlador paso a paso, y luego se activará el método channelRead () del ServerBootstrapAcceptor que mencionamos anteriormente.

Sondeo de eventos del selector de servidor

Volviendo al código de inicio de ServerBootStrap en el lado del servidor, comienza desde el método bind (). El método bind () de ServerBootStrapt es en realidad el método bind () de su clase padre AbstractBootstrap. Mira el código:

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

En el método doBind0 (), se llama al método execute () de EventLoop y continuamos con el seguimiento:

SingleThreadEventExecutor

private void execute(Runnable task, boolean immediate) {
    boolean inEventLoop = inEventLoop();
    addTask(task);
    if (!inEventLoop) {
        startThread();
        if (isShutdown()) {
            boolean reject = false;
            try {
                if (removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException e) {
                // The task queue does not support removal so the best thing we can do is to just move on and
                // hope we will be able to pick-up the task before its completely terminated.
                // In worst case we will log on termination.
            }
            if (reject) {
                reject();
            }
        }
    }

    if (!addTaskWakesUp && immediate) {
        wakeup(inEventLoop);
    }
}

En execute (), el hilo se crea y se agrega a la cola de tareas en serie sin bloqueo de EventLoop. Nos centramos en el método startThread () y seguimos mirando el código fuente:

private void startThread() {
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                boolean success = false;
                try {
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }

private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    //*
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    for (;;) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }

                    // Check if confirmShutdown() was called at the end of the loop.
                    if (success && gracefulShutdownStartTime == 0) {
                        if (logger.isErrorEnabled()) {
                            logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                    SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
                                    "be called before run() implementation terminates.");
                        }
                    }

                    try {
                        // Run all remaining tasks and shutdown hooks. At this point the event loop
                        // is in ST_SHUTTING_DOWN state still accepting tasks which is needed for
                        // graceful shutdown with quietPeriod.
                        for (;;) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }

                        // Now we want to make sure no more tasks can be added from this point. This is
                        // achieved by switching the state. Any new tasks beyond this point will be rejected.
                        for (;;) {
                            int oldState = state;
                            if (oldState >= ST_SHUTDOWN || STATE_UPDATER.compareAndSet(
                                    SingleThreadEventExecutor.this, oldState, ST_SHUTDOWN)) {
                                break;
                            }
                        }

                        // We have the final set of tasks in the queue now, no more can be added, run all remaining.
                        // No need to loop here, this is the final pass.
                        confirmShutdown();
                    } finally {
                        try {
                            cleanup();
                        } finally {
                            // Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify
                            // the future. The user may block on the future and once it unblocks the JVM may terminate
                            // and start unloading classes.
                            // See https://github.com/netty/netty/issues/6596.
                            FastThreadLocal.removeAll();

                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                            threadLock.countDown();
                            int numUserTasks = drainTasks();
                            if (numUserTasks > 0 && logger.isWarnEnabled()) {
                                logger.warn("An event executor terminated with " +
                                        "non-empty task queue (" + numUserTasks + ')');
                            }
                            terminationFuture.setSuccess(null);
                        }
                    }
                }
            }
        });
    }

Encontramos que startThread () finalmente llama al método SingleThreadEventExecutor.this.run (), este es el objeto NioEventLoop:

@Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO

                    case SelectStrategy.SELECT:
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                //*解决JDK空轮训的bug
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // This update is just to help block unnecessary selector wakeups
                            // so use of lazySet is ok (no race condition)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                    // the selector and retry. https://github.com/netty/netty/issues/8566
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                            //针对不同的轮训事件进行处理
                            processSelectedKeys();
                        }
                    } finally {
                        // Ensure we always run tasks.
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }

                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                // Harmless exception - log anyway
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Error e) {
                throw (Error) e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                try {
                    if (isShuttingDown()) {
                        closeAll();
                        if (confirmShutdown()) {
                            return;
                        }
                    }
                } catch (Error e) {
                    throw (Error) e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }

Finalmente vi el código familiar. El código anterior utiliza principalmente un bucle infinito para sondear continuamente SelectionKey. El método select () se usa principalmente para resolver el error de sondeo vacío de JDK, y processSelectedKeys () es para lidiar con diferentes eventos de sondeo. Si el cliente tiene datos para escribir, eventualmente llamará al método doReadMessages () de AbstractNioMessageChannel. En conclusión:

AbstractBootstrap.doBind0-> SingleThreadEventExecutor.execute-> SingleThreadEventExecutor.run->

NioEventLoop.run (不断 轮训 selectKeys) -> AbstractNioChannel.read-> AbstractNioMessageChannel.read-> NioServerSocketChannel.doReadMessages

1. El sondeo del evento Selector en Netty comienza desde el método execute () de EventLoop.

2. En el método execute () de EventLoop, se creará un hilo independiente para cada tarea y se guardará en la cola de tareas en serie desbloqueada.

3. Cada tarea de la cola de tareas del hilo realmente llama al método run () de NioEventLoop.

4. Llame a processSelectedKeys () en el método de ejecución para procesar los eventos de sondeo.

El proceso de agregar Handler

El proceso de agregar un manejador del lado del servidor es algo diferente al del lado del cliente. Como EventLoopGroup, hay dos manejadores del lado del servidor: uno es el manejador establecido por el método handler () y el otro es el childHandler establecido por el método childHandler (). A través del análisis anterior de bossGroup y workerGroup, podemos adivinar audazmente aquí: el controlador está relacionado con el proceso de aceptación. Es decir, el controlador es responsable de procesar la nueva solicitud de acceso a la conexión del cliente, y el controlador infantil es responsable de la interacción IO con la conexión del cliente. Entonces, ¿es este realmente el caso? Seguimos usando código para probar. En los capítulos anteriores, hemos aprendido que ServerBootstrap reescribe el método init (), y el controlador también se agrega a este método:

@Override
    void init(Channel channel) {
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
        }
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

En el método initChannel () del código anterior, primero obtenga un controlador a través del método handler (). Si el controlador obtenido no está vacío, agréguelo a la canalización. Luego, agregue una instancia de ServerBootstrapAcceptor. Entonces, ¿qué objeto es devuelto por el método handler () aquí? De hecho, devuelve el campo del controlador, y este campo es lo que configuramos en el código de inicio del lado del servidor:

ServerBootstrap b = new ServerBootstrap (); 
b.group (bossGroup, workerGroup)

En este momento, el controlador en proceso es el siguiente:

De acuerdo con el análisis de nuestro código de cliente original, especificamos que el canal está vinculado a eventLoop (en este caso, NioServerSocketChannel está vinculado a bossGroup), el evento fireChannelRegistered se activará en la canalización, y luego se activará el método initChannel () de ChannelInitializer transferir. Por lo tanto, una vez que se completa el enlace, la canalización en este momento es la siguiente:

Cuando analizamos bossGroup y workerGroup anteriormente, ya sabíamos que el método channelRead () de ServerBootstrapAcceptor establecería el controlador para el Channel recién creado y lo registraría en un eventLoop, a saber:

@Override
        @SuppressWarnings("unchecked")
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

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

Y el childHandler aquí es el controlador que configuramos en el código de inicio del lado del servidor:

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {

                }
            }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);

Los siguientes pasos son básicamente claros para nosotros: cuando el cliente se conecta al registro del canal, activará el método initChannel () del ChannelInitializer. Finalmente, resumamos la diferencia y la conexión entre el controlador del servidor y el controlador de niños:

1. En la canalización del servidor NioServerSocketChannel, se agregan el controlador y ServerBootstrapAcceptor.

2. Cuando haya una nueva solicitud de conexión de cliente, llame al método channelRead () de ServerBootstrapAcceptor para crear esta conexión

NioSocketChannel y agregue childHandler a la canalización correspondiente a NioSocketChannel, y vincule este canal a

En un eventLoop en workerGroup.

3. El controlador trabaja en la fase de aceptación y procesa la solicitud de conexión del cliente.

4. El childHandler entra en vigor después de que se establece la conexión del cliente, y es responsable de la interacción IO de la conexión del cliente.

Finalmente, mire una imagen para profundizar su comprensión. La siguiente figura describe el proceso de cambio desde la inicialización del servidor hasta el nuevo acceso a la conexión:

Supongo que te gusta

Origin blog.csdn.net/madongyu1259892936/article/details/111322258
Recomendado
Clasificación