Análisis del código fuente principal de Netty (2), análisis del código fuente del proceso de solicitud de recepción del servidor de Netty

Directorio de artículos de la serie

Análisis del código fuente del núcleo de Netty (1), análisis del código fuente del proceso de inicio del lado del servidor de Netty Análisis del
código fuente del núcleo de Netty (2), análisis del código fuente del proceso de solicitud de recepción del lado del servidor de Netty Análisis del
código fuente del núcleo de Netty (3) La clave para la solicitud comercial ejecución - ChannelPipeline, ChannelHandler, ChannelHandlerContext análisis de código fuente Análisis de
código fuente principal de Netty (4) análisis de código fuente de detección de latidos
Análisis de código fuente principal de Netty (5) componente principal Análisis de código fuente de EventLoop

1. Análisis del código fuente del proceso de aceptación de solicitud de conexión

Cuando se inicia el servidor Netty, ejecuta el método de ejecución de NioEventLoop, que es un ciclo sin fin, que se repite continuamente para determinar si ocurre un evento:

// io.netty.channel.nio.NioEventLoop#run
@Override
protected void run() {
    
    
    for (;;) {
    
    
        try {
    
    
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
    
    
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));

                    // 'wakenUp.compareAndSet(false, true)' is always evaluated
                    // before calling 'selector.wakeup()' to reduce the wake-up
                    // overhead. (Selector.wakeup() is an expensive operation.)
                    //
                    // However, there is a race condition in this approach.
                    // The race condition is triggered when 'wakenUp' is set to
                    // true too early.
                    //
                    // 'wakenUp' is set to true too early if:
                    // 1) Selector is waken up between 'wakenUp.set(false)' and
                    //    'selector.select(...)'. (BAD)
                    // 2) Selector is waken up between 'selector.select(...)' and
                    //    'if (wakenUp.get()) { ... }'. (OK)
                    //
                    // In the first case, 'wakenUp' is set to true and the
                    // following 'selector.select(...)' will wake up immediately.
                    // Until 'wakenUp' is set to false again in the next round,
                    // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
                    // any attempt to wake up the Selector will fail, too, causing
                    // the following 'selector.select(...)' call to block
                    // unnecessarily.
                    //
                    // To fix this problem, we wake up the selector again if wakenUp
                    // is true immediately after selector.select(...).
                    // It is inefficient in that it wakes up the selector for both
                    // the first case (BAD - wake-up required) and the second case
                    // (OK - no wake-up required).

                    if (wakenUp.get()) {
    
    
                        selector.wakeup();
                    }
                    // fall through
                default:
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
    
    
                try {
    
    
                    processSelectedKeys();
                } finally {
    
    
                    // Ensure we always run tasks.
                    runAllTasks();
                }
            } else {
    
    
                final long ioStartTime = System.nanoTime();
                try {
    
    
                	// 关键方法
                    processSelectedKeys();
                } finally {
    
    
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
    
    
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
    
    
            if (isShuttingDown()) {
    
    
                closeAll();
                if (confirmShutdown()) {
    
    
                    return;
                }
            }
        } catch (Throwable t) {
    
    
            handleLoopException(t);
        }
    }
}

1. El valor del evento

Los eventos de lectura, escritura, conexión y aceptación están todos definidos en SelectionKey, y el desplazamiento se usa para el cálculo

// 1
public static final int OP_READ = 1 << 0;
// 4
public static final int OP_WRITE = 1 << 2;
// 8
public static final int OP_CONNECT = 1 << 3;
// 16
public static final int OP_ACCEPT = 1 << 4;

2. ProcessSelectedKeys obtener evento

// io.netty.channel.nio.NioEventLoop#processSelectedKeys
private void processSelectedKeys() {
    
    
    if (selectedKeys != null) {
    
    
        processSelectedKeysOptimized();
    } else {
    
    
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

// io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
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 registered 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
        // 判断事件,selectKeys最终执行的方法
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
    
    
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
    
    
        unsafe.close(unsafe.voidPromise());
    }
}

Cuando tenemos una conexión de cliente, podemos ver que el valor de readyOps es 16, correspondiente al evento ACCEPT también es 16, y el método unsafe.read() se ejecutará en este momento.
inserte la descripción de la imagen aquí
NioMessageUnsafe es una clase interna de AbstractNioMessageChannel:

// io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe
private final class NioMessageUnsafe extends AbstractNioUnsafe {
    
    

    private final List<Object> readBuf = new ArrayList<Object>();

    @Override
    public void read() {
    
    
    	// 检查该eventLoop是否是当前线程
        assert eventLoop().inEventLoop();
        final ChannelConfig config = config(); // 拿到config
        final ChannelPipeline pipeline = pipeline(); // 拿到pipeline
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.reset(config);

        boolean closed = false;
        Throwable exception = null;
        try {
    
    
            try {
    
    
                do {
    
    
                	// readBuf是一个list,此处循环执行该方法
                	// doReadMessages是读取boss线程中的NioServerSocketChannel接收到的请求,并把这些请求放进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;
                // 循环调用handler中的ChannelRead方法
                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();
            }
        }
    }
}

(1) método doReadMessages

// io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    
    
	// 实际上调用NIO的的accept方法,获取SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
    
    
        if (ch != null) {
    
    
        	// 将NIO的SocketChannel包装成NioSocketChannel
            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;
}

Descubrimos que doReadMessages en realidad llama al método de aceptación de SocketChannel de NIO para obtener SocketChannel de NIO.
Luego use el NioSocketChannel de Netty para envolver el SocketChannel de NIO y finalmente agréguelo al contenedor buf.

(2) método fireChannelRead de canalización

Después de que el método doReadMessages obtiene el NioSocketChannel, fireChannelRead pasa estos NioSocketChannels como parámetros.

// io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    
    
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
    
    
            notifyHandlerException(t);
        }
    } else {
    
    
        fireChannelRead(msg);
    }
}

Descubrimos que se llamará al método channelRead de channelHandler,

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    ctx.fireChannelRead(msg);
}
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    
    
    invokeChannelRead(findContextInbound(), msg);
    return this;
}
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

Descubrimos que cuando se llama al método channelRead, se llama a ctx.fireChannelRead(msg) para pasar la solicitud al siguiente controlador en la canalización, y en la canalización, el controlador se almacena en una cadena.

ServerBootstrapAcceptor es el último controlador de la canalización, y su método channelRead se implementa en la clase principal ServerBootStrap. Sigamos examinando el método channelRead de ServerBootstrapAcceptor.

注意!此处的Handler,并不是child的Handler!而是bossGroup中定义的Handler!
woekerGroup定义的Handler在children中!

(3) método channelRead de ServerBootstrapAcceptor

// io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead
@Override
@SuppressWarnings("unchecked")
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 {
    
     // 将客户端连接注册到worker线程池
    	// 此处的childGroup,就是我们的workergroup,child就是NioSocketChannel
        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);
    }
}

Finalmente, llame al método de registro de workGroup (childGroup) para registrar NioSocketChannel en workerGroup (childGroup).

注意!workGroup是我们命名的,Netty称为childGroup

3. El proceso de registro de workerGroup

De acuerdo con el análisis anterior, después de que bossGroup obtenga NioSocketChannel, llamará al método de registro de workerGroup (childGroup) para registrarse:

// io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel)
@Override
public ChannelFuture register(Channel channel) {
    
    
    return next().register(channel);
}

El siguiente método sondeará para obtener el siguiente subproceso del grupo de trabajadores (el grupo de trabajadores generalmente creará varios subprocesos, el valor predeterminado es la cantidad de núcleos de CPU * 2) y llamará al método de registro para pasar el NioSocketChannel.

// io.netty.util.concurrent.DefaultEventExecutorChooserFactory.GenericEventExecutorChooser#next
@Override
public EventExecutor next() {
    
    
	// 取下一个执行器
    return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}

Sigamos analizando el método de registro:

// io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel)
@Override
public ChannelFuture register(Channel channel) {
    
    
	// 对Channel进一步包装
    return register(new DefaultChannelPromise(channel, this));
}
// io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.ChannelPromise)
@Override
public ChannelFuture register(final ChannelPromise promise) {
    
    
    ObjectUtil.checkNotNull(promise, "promise");
    // 真正的register的方法
    promise.channel().unsafe().register(this, promise);
    return promise;
}
// io.netty.channel.AbstractChannel.AbstractUnsafe#register
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    
    
    if (eventLoop == null) {
    
    
        throw new NullPointerException("eventLoop");
    }
    if (isRegistered()) {
    
    
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    if (!isCompatible(eventLoop)) {
    
    
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    AbstractChannel.this.eventLoop = eventLoop;

    if (eventLoop.inEventLoop()) {
    
    
        register0(promise);
    } else {
    
    
        try {
    
    
            eventLoop.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                	// 进入到这里
                    register0(promise);
                }
            });
        } catch (Throwable t) {
    
    
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }
}

En el método register0, se ejecuta el registro del método doRegister, y se juzga si hay tareas que puedan existir en el Canal y se ejecutan.

// io.netty.channel.AbstractChannel.AbstractUnsafe#register0
private void register0(ChannelPromise promise) {
    
    
    try {
    
    
        // check if the channel is still open as it could be closed in the mean time when the register
        // call was outside of the eventLoop
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
    
    
            return;
        }
        boolean firstRegistration = neverRegistered;
        // 执行注册
        doRegister();
        neverRegistered = false;
        registered = true;

        // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
        // user may already fire events through the pipeline in the ChannelFutureListener.
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        // Only fire a channelActive if the channel has never been registered. This prevents firing
        // multiple channel actives if the channel is deregistered and re-registered.
        if (isActive()) {
    
    
            if (firstRegistration) {
    
    
            	// 第一次注册执行ChannelActive方法
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
    
    
                // This channel was registered before and autoRead() is set. This means we need to begin read
                // again so that we process inbound data.
                //
                // See https://github.com/netty/netty/issues/4805
                // 开始读取
                beginRead();
            }
        }
    } catch (Throwable t) {
    
    
        // Close the channel directly to avoid FD leak.
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}
// io.netty.channel.nio.AbstractNioChannel#doRegister
@Override
protected void doRegister() throws Exception {
    
    
    boolean selected = false;
    for (;;) {
    
    
        try {
    
    
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
    
    
            if (!selected) {
    
    
                // Force the Selector to select now as the "canceled" SelectionKey may still be
                // cached and not removed because no Select.select(..) operation was called yet.
                eventLoop().selectNow();
                selected = true;
            } else {
    
    
                // We forced a select operation on the selector before but the SelectionKey is still cached
                // for whatever reason. JDK bug ?
                throw e;
            }
        }
    }
}

4. método beginRead

// io.netty.channel.AbstractChannel.AbstractUnsafe#beginRead
@Override
public final void beginRead() {
    
    
    assertEventLoop();

    if (!isActive()) {
    
    
        return;
    }

    try {
    
    
        doBeginRead();
    } catch (final Exception e) {
    
    
        invokeLater(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}

// io.netty.channel.nio.AbstractNioChannel#doBeginRead
@Override
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);
    }
}

En este punto, el lado del servidor recibe la solicitud de conexión enviada por el lado del cliente y registra la solicitud en el grupo de trabajadores, y el grupo de trabajadores comienza a recibir datos.
inserte la descripción de la imagen aquí

5. Netty acepta el resumen del código fuente del proceso de solicitud

Proceso general: aceptar la conexión---->crear un nuevo NioSocketChannel---------->registrar en un Worker EventLoop------->registrar el evento de lectura seleccionado.

1) El servidor sondea el evento Aceptar y llama al método de lectura de inseguro después de recibir el evento.Este inseguro es una clase interna de ServerSocket.
2) doReadMessages se usa para crear un objeto NioSocketChannel, que envuelve el cliente Nio Channel de JDK. Este método creará una canalización relacionada, insegura, una configuración similar a la creación de ServerSocketChanel.
3) Luego ejecute el método pipeline.fireChannelRead, y en el método channelRead de ServerBootstrapAcceptor, llame al método de registro de workerGroup (childGroup) para registrarse.
4) Después de un registro exitoso, comience a escuchar el evento de lectura.

Supongo que te gusta

Origin blog.csdn.net/A_art_xiang/article/details/130320465
Recomendado
Clasificación