Lectura del código fuente de Netty (2) - resumen del código fuente del servidor

Arriba, tenemos una comprensión general del código fuente del cliente, para que sea más fácil entender el código fuente del servidor Analizaremos la diferencia entre el servidor y el cliente.

Tabla de contenido

la diferencia

 ④

 ①


la diferencia

 ④

Cliente: .option(ChannelOption.TCP_NODELAY, verdadero)

En el protocolo TCP/IP, no importa cuántos datos se envíen, siempre se agrega un encabezado de protocolo delante de los datos. Al mismo tiempo, cuando la otra parte recibe los datos, también necesita enviar ACK para indicar la confirmación. Para utilizar el ancho de banda de la red tanto como sea posible, TCP siempre quiere enviar la mayor cantidad de datos posible. Esto implica un algoritmo llamado Nagle, cuyo propósito es enviar grandes bloques de datos tanto como sea posible para evitar inundar la red con muchos bloques de datos pequeños.
TCP_NODELAY se utiliza para habilitar o sobre el algoritmo de Nagle. Si se requiere un alto rendimiento en tiempo real y los datos se envían inmediatamente, establezca esta opción en verdadero para deshabilitar el algoritmo de Nagle; si desea reducir la cantidad de veces de envío y reducir la interacción de la red, configúrelo en falso y espere un cierto tamaño para ser acumulado antes de enviar. El valor predeterminado es falso.

Servidor: .option(ChannelOption.SO_BACKLOG, 100)

El módulo kernel TCP del lado del servidor mantiene dos colas, que llamamos A y B. Cuando el cliente se conecta al servidor, el módulo kernel TCP agrega la conexión del cliente a la cola A durante el segundo protocolo de enlace y luego la tercera vez. protocolo de enlace, TCP mueve la conexión del cliente de la cola A a la cola B. Una vez completada la conexión, la aceptación de la aplicación regresará y la aceptación eliminará la conexión que completó el protocolo de enlace de tres vías de la cola B.

La suma de las longitudes de la cola A y la cola B es el retraso. Cuando la suma de las longitudes de las colas A y B es mayor que el retraso, el núcleo TCP rechazará la nueva conexión. Por lo tanto, si el la acumulación es demasiado pequeña, es posible que la velocidad de aceptación no pueda mantenerse. La cola AB está llena, lo que hace que los nuevos clientes no puedan conectarse. Cabe señalar que la acumulación no tiene efecto en la cantidad de conexiones admitidas por el programa. El backlog solo afecta a conexiones que no han sido sacadas por accept

 También se usa comúnmente: .option(ChannelOption.SO_REUSEADDR, true)

El parámetro indica que la dirección local y el puerto pueden reutilizarse. Por ejemplo, si un proceso del servidor ocupa el puerto TCP 80 para escuchar, se devolverá un error si el puerto se vuelve a escuchar. El uso de este parámetro puede resolver el problema. Este parámetro permite compartir el
puerto, que se usa más comúnmente en programas de servidor,

Cliente: .channel (NioSocketChannel.class)

Servidor: .channel (NioServerSocketChannel.class)

La forma en que se cargan y el proceso de determinación del tipo es el mismo, así que centrémonos en la diferencia en la creación de instancias.

Similar a NioSocketChannel, se llama al método newSocket, pero la siguiente llamada es diferente. El servidor llama al método openServerSocketChannel, mientras que el cliente llama al método openSocketChannel.Como su nombre lo indica, uno es Java SocketChannel en el lado del cliente y el otro es Java ServerSocketChannel en el lado del servidor.

 A continuación, se llama al método sobrecargado y se pasa SelectionKey.OP_ACCEPT aquí. Los amigos que tienen conocimiento sobre Java NIO Socket entienden que Java NIO es un modo Reactor, y usamos selectores para implementar la multiplexación de E/S. Al principio, el el servidor necesita escuchar la solicitud de conexión del cliente, por lo que aquí configuramos  SelectionKey.OP_ACCEPT. ¿Todavía tenemos impresiones de clientes? Compara la imagen de abajo

 Luego llame al constructor paso a paso, al igual que el código del cliente, creará una instancia insegura y canalización

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

Pero newUnsafe es diferente. El campo no seguro en el lado del servidor es en realidad una instancia de AbstractNioMessageChannel#AbstractNioUnsafe. Y el cliente es una instancia de AbstractNioByteChannel#NioByteUnsafe

 ①

En el lado del cliente, solo proporcionamos un objeto EventLoopGroup, mientras que en la inicialización del lado del servidor, configuramos dos EventLoopGroups, uno es bossGroup y el otro es workerGroup. Entonces, ¿para qué se usan estos dos EventLoopGroups? De hecho, el bossGroup es se utiliza para la aceptación del servidor, es decir, se utiliza para procesar la solicitud de conexión del cliente. El grupo de trabajadores es en realidad el que realmente hace el trabajo, y son responsables de la operación de E/S del canal de conexión del cliente. Como se muestra abajo

BossGroup en el lado 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 de WorkerGroup para vincularlo a esta conexión de cliente central. Luego el siguiente proceso de interacción entre el servidor y el cliente estará todo en el EventLoop asignado. Sigamos el análisis del código fuente.

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

El código anterior hace dos cosas. 1 es ingresar el método de grupo de AbstractBootstrap para especificar el atributo de grupo, que está directamente aquí en el lado del cliente. 2 es especificar el atributo childGroup de ServerBootstrap. Después de inicializar las propiedades, ¿dónde se usan?

bossGroup : AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister asocia boosGroup con NioServerSocketChannsl

ChannelFuture regFuture = config().group().register(channel);

 grupo de trabajo : sigue siendo el init (canal) que aparece en el método initAndRegister.

   void init(Channel channel) {
        // 只贴出和childGroup有关的关键代码
        final EventLoopGroup currentChildGroup = childGroup;
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

El método init se reescribe en ServerBootstrap y childGroup se encapsula principalmente en la clase ServerBootstrapAcceptor, por lo que nos centramos en esta clase.

Hicimos clic y vimos que resultó ser una clase interna estática. Las operaciones relacionadas con childGroup se colocan principalmente en el método channelRead reescrito por la clase.

 childGroup.register(child).addListener(...);

El niño aquí es un NioSocketChannel, lo que significa que un EventLoop en el grupo de trabajo está asociado con el NioSocketChannel.

Entonces, ¿cuándo se llama a este método channelRead? Cuando un cliente se conecta al servidor, el ServerSocketChannel de Java NIO subyacente tendrá un  evento listo SelectionKey.OP_ACCEPT  y luego llamará a NioServerSocketChannel.doReadMessages.

            SocketChannel ch = SocketUtils.accept(javaChannel());
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }

 Obtenga la conexión con el cliente a través del método de aceptación y, a continuación, encapsúlela como un NioSocketChannel. El paso this se refiere al NioServerSocketChannel, que es el canal principal que encapsula el NioSocketChannel. Luego, a través del mecanismo ChannelPipeline de Netty, el evento de lectura se envía a cada controlador paso a paso, por lo que activará el ServerBootstrapAcceptor.channelRead que mencionamos anteriormente.

Al igual que boosGroup y workGroup, handler y childHandler aparecen aquí, entonces, ¿será el mismo que los dos anteriores? ¿Uno maneja las conexiones y el otro los eventos de IO? Veamos el código.

En ④ mencionamos la reescritura del método init en ServerBootstrap. También implica la operación del manipulador.

        final ChannelHandler currentChildHandler = childHandler; // 1

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

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

Primero mire 2 (nota 2 en el código anterior), donde config.handler() es el controlador especificado por el código del servidor .handler(new LoggingHandler(LogLevel.INFO)), si no está vacío, agréguelo a la canalización .

 Luego mire 1 (el comentario de código anterior 1), el childHandler aquí sigue siendo la propiedad de construir ServerBootstrapAcceptor, haga clic en esta clase y descubra que este childHandler está en el método channelRead reescrito por ServerBootstrapAcceptor (¿recuerda cuándo se llamó? Después de que se establezca la conexión después)

child.pipeline().addLast(childHandler);

Recuerde a este niño, es un NioSocketChannel y childHandler se agrega a su canalización.

En resumen, lo siguiente es

  • El controlador funciona en la fase de aceptación, maneja la solicitud de conexión del cliente.
  • childHandler funciona después de que se establece la conexión del cliente y es responsable de la interacción de E/S de la conexión del cliente.

Supongo que te gusta

Origin blog.csdn.net/wai_58934/article/details/127860262
Recomendado
Clasificación